Merge branch 'master' into exp-nosql
* master:
Fix all of our pom.xml versions to be 2.1-SNAPSHOT
Use internal templates to simplify minor formatting commands.
Convert 3 email classes to file based templates.
Convert the Abandoned and MergeFail email classes to templates
Use a template to set the contents of the CommentEmails.
Use a template to set the footer on ChangeEmails.
Use a template to set the contents of the MergedEmails.
Use a template to set the subject line.
Add framework for using velocity templates in email classes
Add ability to deactivate a user when they leave the project.
Conflicts:
gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
Change-Id: Iac92b56e10f9cc4063fc0ec3cea6c79a5a087994
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 591ffa5..63b5192 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -630,6 +630,12 @@
* `JDBC`
+
Connect using a JDBC driver class name and URL.
++
+* `NOSQL_HEAP_FILE`
++
+Connect to a locally stored NoSQL database. Similar to H2, except
+its a binary tree NoSQL implementation that is only suitable for
+development and testing. Do not use for a production server.
+
If not specified, database.driver and database.url are used as-is,
@@ -648,8 +654,8 @@
+
For POSTGRESQL or MYSQL, the name of the database on the server.
+
-For H2, this is the path to the database, and if not absolute is
-relative to `'$site_path'`.
+For H2 and NOSQL_HEAP_FILE, this is the path to the database,
+and if not absolute is relative to `'$site_path'`.
[[database.username]]database.username::
+
@@ -1489,6 +1495,8 @@
If multiple values are supplied, the daemon will listen on all
of them.
+
+To disable the internal SSHD, set listenAddress to `off`.
++
By default, *:29418.
[[sshd.reuseAddress]]sshd.reuseAddress::
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index 25194a9..6d543d5 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -17,6 +17,7 @@
gwtjsonrpc <<apache2,Apache License 2.0>>
gwtorm <<apache2,Apache License 2.0>>
Google Gson <<apache2,Apache License 2.0>>
+Google Guava Libraries <<apache2,Apache License 2.0>>
Google Web Toolkit <<apache2,Apache License 2.0>>
Guice <<apache2,Apache License 2.0>>
Apache Commons Codec <<apache2,Apache License 2.0>>
@@ -38,6 +39,7 @@
mime-util <<apache2,Apache License 2.0>>
Jetty <<apache2,Apache License 2.0>>, or link:http://www.eclipse.org/legal/epl-v10.html[EPL]
Google Code Prettify <<apache2,Apache License 2.0>>
+Google Protobuf <<apache2,Apache License 2.0>>
JGit <<jgit,New-Style BSD>>
JSch <<sshd,New-Style BSD>>
PostgreSQL JDBC Driver <<postgresql,New-Style BSD>>
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/auth/openid/OpenIdProviderPattern.java b/gerrit-common/src/main/java/com/google/gerrit/common/auth/openid/OpenIdProviderPattern.java
index 731d765..ec51941 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/auth/openid/OpenIdProviderPattern.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/auth/openid/OpenIdProviderPattern.java
@@ -15,6 +15,7 @@
package com.google.gerrit.common.auth.openid;
import com.google.gerrit.reviewdb.AccountExternalId;
+import com.google.gwtorm.client.Column;
public class OpenIdProviderPattern {
public static OpenIdProviderPattern create(String pattern) {
@@ -24,7 +25,10 @@
return r;
}
+ @Column(id = 1)
protected boolean regex;
+
+ @Column(id = 2)
protected String pattern;
protected OpenIdProviderPattern() {
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
index 717a492..27b6ac9 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/HostPageData.java
@@ -21,5 +21,6 @@
public class HostPageData {
public Account account;
public AccountDiffPreference accountDiffPref;
+ public String xsrfToken;
public GerritConfig config;
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
index 048d440..0597ee9 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
@@ -16,6 +16,7 @@
import com.google.gerrit.prettify.client.ClientSideFormatter;
import com.google.gerrit.prettify.common.EditList;
+import com.google.gerrit.prettify.common.LineEdit;
import com.google.gerrit.prettify.common.PrettyFormatter;
import com.google.gerrit.prettify.common.SparseFileContent;
import com.google.gerrit.prettify.common.SparseHtmlFile;
@@ -25,8 +26,6 @@
import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
import com.google.gerrit.reviewdb.Patch.ChangeType;
-import org.eclipse.jgit.diff.Edit;
-
import java.util.List;
public class PatchScript {
@@ -42,7 +41,7 @@
protected AccountDiffPreference diffPrefs;
protected SparseFileContent a;
protected SparseFileContent b;
- protected List<Edit> edits;
+ protected List<LineEdit> edits;
protected DisplayMethod displayMethodA;
protected DisplayMethod displayMethodB;
protected CommentDetail comments;
@@ -53,7 +52,7 @@
public PatchScript(final Change.Key ck, final ChangeType ct, final String on,
final String nn, final List<String> h, final AccountDiffPreference dp,
final SparseFileContent ca, final SparseFileContent cb,
- final List<Edit> e, final DisplayMethod ma, final DisplayMethod mb,
+ final List<LineEdit> e, final DisplayMethod ma, final DisplayMethod mb,
final CommentDetail cd, final List<Patch> hist, final boolean hf,
final boolean id) {
changeId = ck;
@@ -170,7 +169,7 @@
return f;
}
- public List<Edit> getEdits() {
+ public List<LineEdit> getEdits() {
return edits;
}
diff --git a/gerrit-ehcache/.gitignore b/gerrit-ehcache/.gitignore
new file mode 100644
index 0000000..903c6c8
--- /dev/null
+++ b/gerrit-ehcache/.gitignore
@@ -0,0 +1,4 @@
+/target
+/.classpath
+/.project
+/.settings/org.maven.ide.eclipse.prefs
diff --git a/gerrit-ehcache/.settings/org.eclipse.core.resources.prefs b/gerrit-ehcache/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..82eb859
--- /dev/null
+++ b/gerrit-ehcache/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Tue Sep 02 16:59:24 PDT 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/gerrit-ehcache/.settings/org.eclipse.core.runtime.prefs b/gerrit-ehcache/.settings/org.eclipse.core.runtime.prefs
new file mode 100644
index 0000000..8667cfd
--- /dev/null
+++ b/gerrit-ehcache/.settings/org.eclipse.core.runtime.prefs
@@ -0,0 +1,3 @@
+#Tue Sep 02 16:59:24 PDT 2008
+eclipse.preferences.version=1
+line.separator=\n
diff --git a/gerrit-ehcache/.settings/org.eclipse.jdt.core.prefs b/gerrit-ehcache/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..04afc7f
--- /dev/null
+++ b/gerrit-ehcache/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,268 @@
+#Tue May 12 17:44:13 PDT 2009
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=16
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=true
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=80
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=2
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/gerrit-ehcache/.settings/org.eclipse.jdt.ui.prefs b/gerrit-ehcache/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..d4218a5
--- /dev/null
+++ b/gerrit-ehcache/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,61 @@
+#Wed Jul 29 11:31:38 PDT 2009
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_Google Format
+formatter_settings_version=11
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=99
+org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=false
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=true
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=false
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=false
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_blocks=false
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/gerrit-ehcache/pom.xml b/gerrit-ehcache/pom.xml
new file mode 100644
index 0000000..a7c6cf5
--- /dev/null
+++ b/gerrit-ehcache/pom.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2010 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>com.google.gerrit</groupId>
+ <artifactId>gerrit-parent</artifactId>
+ <version>2.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>gerrit-ehcache</artifactId>
+ <name>Gerrit Code Review - Ehcache Bindings</name>
+
+ <description>
+ Bindings to Ehcache
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>net.sf.ehcache</groupId>
+ <artifactId>ehcache-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.gerrit</groupId>
+ <artifactId>gerrit-server</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.protobuf</groupId>
+ <artifactId>protobuf-java</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/EhcachePoolImpl.java b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/EhcachePoolImpl.java
new file mode 100644
index 0000000..760b88f
--- /dev/null
+++ b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/EhcachePoolImpl.java
@@ -0,0 +1,287 @@
+// Copyright (C) 2009 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.ehcache;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import com.google.gerrit.lifecycle.LifecycleListener;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.cache.Cache;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.cache.CachePool;
+import com.google.gerrit.server.cache.CacheProvider;
+import com.google.gerrit.server.cache.EntryCreator;
+import com.google.gerrit.server.cache.EvictionPolicy;
+import com.google.gerrit.server.cache.ProxyCache;
+import com.google.gerrit.server.config.ConfigUtil;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Inject;
+import com.google.inject.ProvisionException;
+import com.google.inject.Singleton;
+
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Ehcache;
+import net.sf.ehcache.config.CacheConfiguration;
+import net.sf.ehcache.config.Configuration;
+import net.sf.ehcache.config.DiskStoreConfiguration;
+import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
+
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Pool of all declared caches created by {@link CacheModule}s. */
+@Singleton
+public class EhcachePoolImpl implements CachePool {
+ private static final Logger log =
+ LoggerFactory.getLogger(EhcachePoolImpl.class);
+
+ public static class Module extends LifecycleModule {
+ @Override
+ protected void configure() {
+ bind(CachePool.class).to(EhcachePoolImpl.class);
+ bind(EhcachePoolImpl.class);
+ listener().to(EhcachePoolImpl.Lifecycle.class);
+ }
+ }
+
+ public static class Lifecycle implements LifecycleListener {
+ private final EhcachePoolImpl cachePool;
+
+ @Inject
+ Lifecycle(final EhcachePoolImpl cachePool) {
+ this.cachePool = cachePool;
+ }
+
+ @Override
+ public void start() {
+ cachePool.start();
+ }
+
+ @Override
+ public void stop() {
+ cachePool.stop();
+ }
+ }
+
+ private final Config config;
+ private final SitePaths site;
+
+ private final Object lock = new Object();
+ private final Map<String, CacheProvider<?, ?>> caches;
+ private CacheManager manager;
+
+ @Inject
+ EhcachePoolImpl(@GerritServerConfig final Config cfg, final SitePaths site) {
+ this.config = cfg;
+ this.site = site;
+ this.caches = new HashMap<String, CacheProvider<?, ?>>();
+ }
+
+ private void start() {
+ synchronized (lock) {
+ if (manager != null) {
+ throw new IllegalStateException("Cache pool has already been started");
+ }
+
+ try {
+ System.setProperty("net.sf.ehcache.skipUpdateCheck", "" + true);
+ } catch (SecurityException e) {
+ // Ignore it, the system is just going to ping some external page
+ // using a background thread and there's not much we can do about
+ // it now.
+ }
+
+ manager = new CacheManager(new Factory().toConfiguration());
+ for (CacheProvider<?, ?> p : caches.values()) {
+ Ehcache eh = manager.getEhcache(p.getName());
+ EntryCreator<?, ?> c = p.getEntryCreator();
+
+ if (c != null && p.disk()) {
+ c = new ProtobufEntryCreator(c, p.getKeyClass(), p.getValueClass());
+ }
+
+ Cache m;
+ if (c != null) {
+ m = new PopulatingCache(eh, c);
+ } else {
+ m = new SimpleCache(eh);
+ }
+ if (p.disk()) {
+ m = new ProtobufCache(m, p.getKeyClass(), p.getValueClass(), p
+ .getValueProvider());
+ }
+
+ p.bind(m);
+ }
+ }
+ }
+
+ private void stop() {
+ synchronized (lock) {
+ if (manager != null) {
+ manager.shutdown();
+ }
+ }
+ }
+
+ /** <i>Discouraged</i> Get the underlying cache descriptions, for statistics. */
+ public CacheManager getCacheManager() {
+ synchronized (lock) {
+ return manager;
+ }
+ }
+
+ public <K, V> ProxyCache<K, V> register(final CacheProvider<K, V> provider) {
+ synchronized (lock) {
+ if (manager != null) {
+ throw new IllegalStateException("Cache pool has already been started");
+ }
+
+ final String n = provider.getName();
+ if (caches.containsKey(n) && caches.get(n) != provider) {
+ throw new IllegalStateException("Cache \"" + n + "\" already defined");
+ }
+ caches.put(n, provider);
+ return new ProxyCache<K, V>();
+ }
+ }
+
+ private class Factory {
+ private static final int MB = 1024 * 1024;
+ private final Configuration mgr = new Configuration();
+
+ Configuration toConfiguration() {
+ configureDiskStore();
+ configureDefaultCache();
+
+ for (CacheProvider<?, ?> p : caches.values()) {
+ final String name = p.getName();
+ final CacheConfiguration c = newCache(name);
+ c.setMemoryStoreEvictionPolicyFromObject(toPolicy(p.evictionPolicy()));
+
+ c.setMaxElementsInMemory(getInt(name, "memorylimit", p.memoryLimit()));
+
+ c.setTimeToIdleSeconds(0);
+ c.setTimeToLiveSeconds(getSeconds(name, "maxage", p.maxAge()));
+ c.setEternal(c.getTimeToLiveSeconds() == 0);
+
+ if (p.disk() && mgr.getDiskStoreConfiguration() != null) {
+ c.setMaxElementsOnDisk(getInt(name, "disklimit", p.diskLimit()));
+
+ int v = c.getDiskSpoolBufferSizeMB() * MB;
+ v = getInt(name, "diskbuffer", v) / MB;
+ c.setDiskSpoolBufferSizeMB(Math.max(1, v));
+ c.setOverflowToDisk(c.getMaxElementsOnDisk() > 0);
+ c.setDiskPersistent(c.getMaxElementsOnDisk() > 0);
+ }
+
+ mgr.addCache(c);
+ }
+
+ return mgr;
+ }
+
+ private MemoryStoreEvictionPolicy toPolicy(final EvictionPolicy policy) {
+ switch (policy) {
+ case LFU:
+ return MemoryStoreEvictionPolicy.LFU;
+
+ case LRU:
+ return MemoryStoreEvictionPolicy.LRU;
+
+ default:
+ throw new IllegalArgumentException("Unsupported " + policy);
+ }
+ }
+
+ private int getInt(String n, String s, int d) {
+ return config.getInt("cache", n, s, d);
+ }
+
+ private long getSeconds(String n, String s, long d) {
+ d = MINUTES.convert(d, SECONDS);
+ long m = ConfigUtil.getTimeUnit(config, "cache", n, s, d, MINUTES);
+ return SECONDS.convert(m, MINUTES);
+ }
+
+ private void configureDiskStore() {
+ boolean needDisk = false;
+ for (CacheProvider<?, ?> p : caches.values()) {
+ if (p.disk()) {
+ needDisk = true;
+ break;
+ }
+ }
+ if (!needDisk) {
+ return;
+ }
+
+ File loc = site.resolve(config.getString("cache", null, "directory"));
+ if (loc == null) {
+ } else if (loc.exists() || loc.mkdirs()) {
+ if (loc.canWrite()) {
+ final DiskStoreConfiguration c = new DiskStoreConfiguration();
+ c.setPath(loc.getAbsolutePath());
+ mgr.addDiskStore(c);
+ log.info("Enabling disk cache " + loc.getAbsolutePath());
+ } else {
+ log.warn("Can't write to disk cache: " + loc.getAbsolutePath());
+ }
+ } else {
+ log.warn("Can't create disk cache: " + loc.getAbsolutePath());
+ }
+ }
+
+ private void configureDefaultCache() {
+ final CacheConfiguration c = new CacheConfiguration();
+
+ c.setMaxElementsInMemory(1024);
+ c.setMemoryStoreEvictionPolicyFromObject(MemoryStoreEvictionPolicy.LFU);
+
+ c.setTimeToIdleSeconds(0);
+ c.setTimeToLiveSeconds(0 /* infinite */);
+ c.setEternal(true);
+
+ if (mgr.getDiskStoreConfiguration() != null) {
+ c.setMaxElementsOnDisk(16384);
+ c.setOverflowToDisk(false);
+ c.setDiskPersistent(false);
+
+ c.setDiskSpoolBufferSizeMB(5);
+ c.setDiskExpiryThreadIntervalSeconds(60 * 60);
+ }
+
+ mgr.setDefaultCacheConfiguration(c);
+ }
+
+ private CacheConfiguration newCache(final String name) {
+ try {
+ final CacheConfiguration c;
+ c = mgr.getDefaultCacheConfiguration().clone();
+ c.setName(name);
+ return c;
+ } catch (CloneNotSupportedException e) {
+ throw new ProvisionException("Cannot configure cache " + name, e);
+ }
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/PopulatingCache.java b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/PopulatingCache.java
similarity index 79%
rename from gerrit-server/src/main/java/com/google/gerrit/server/cache/PopulatingCache.java
rename to gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/PopulatingCache.java
index 0822cc0..3630733 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/PopulatingCache.java
+++ b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/PopulatingCache.java
@@ -12,10 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.ehcache;
import static java.util.concurrent.TimeUnit.SECONDS;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.server.cache.Cache;
+import com.google.gerrit.server.cache.EntryCreator;
+
import net.sf.ehcache.CacheException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
@@ -59,7 +64,8 @@
}
/**
- * Get the element from the cache, or {@link EntryCreator#missing(Object)} if not found.
+ * Get the element from the cache, or {@link EntryCreator#missing(Object)} if
+ * not found.
* <p>
* The {@link EntryCreator#missing(Object)} method is only invoked if:
* <ul>
@@ -75,9 +81,9 @@
* @return either the cached entry, or {@code missing(key)} if not found.
*/
@SuppressWarnings("unchecked")
- public V get(final K key) {
+ public ListenableFuture<V> get(final K key) {
if (key == null) {
- return creator.missing(key);
+ return Futures.immediateFuture(creator.missing(key));
}
final Element m;
@@ -85,27 +91,32 @@
m = self.get(key);
} catch (IllegalStateException err) {
log.error("Cannot lookup " + key + " in \"" + self.getName() + "\"", err);
- return creator.missing(key);
+ return Futures.immediateFuture(creator.missing(key));
} catch (CacheException err) {
log.error("Cannot lookup " + key + " in \"" + self.getName() + "\"", err);
- return creator.missing(key);
+ return Futures.immediateFuture(creator.missing(key));
}
- return m != null ? (V) m.getObjectValue() : creator.missing(key);
+ if (m != null) {
+ return Futures.immediateFuture((V) m.getObjectValue());
+ }
+ return Futures.immediateFuture(creator.missing(key));
}
- public void remove(final K key) {
+ public ListenableFuture<Void> putAsync(final K key, final V value) {
+ self.put(new Element(key, value));
+ return Futures.immediateFuture(null);
+ }
+
+ public ListenableFuture<Void> removeAsync(final K key) {
if (key != null) {
self.remove(key);
}
+ return Futures.immediateFuture(null);
}
- /** Remove all cached items, forcing them to be created again on demand. */
- public void removeAll() {
+ public ListenableFuture<Void> removeAllAsync() {
self.removeAll();
- }
-
- public void put(K key, V value) {
- self.put(new Element(key, value));
+ return Futures.immediateFuture(null);
}
@Override
diff --git a/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/ProtobufCache.java b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/ProtobufCache.java
new file mode 100644
index 0000000..45cabb7
--- /dev/null
+++ b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/ProtobufCache.java
@@ -0,0 +1,90 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.ehcache;
+
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.server.cache.Cache;
+import com.google.gwtorm.protobuf.CodecFactory;
+import com.google.gwtorm.protobuf.ProtobufCodec;
+import com.google.inject.Provider;
+
+import java.util.concurrent.TimeUnit;
+
+class ProtobufCache<K, V> implements Cache<K, V> {
+ private final Cache<SerializableProtobuf<K>, SerializableProtobuf<V>> cache;
+ private final ProtobufCodec<K> keyCodec;
+ private final ProtobufCodec<V> valueCodec;
+ private final Provider<V> valueProvider;
+ private final Function<SerializableProtobuf<V>, V> unpack;
+
+ ProtobufCache(Cache<SerializableProtobuf<K>, SerializableProtobuf<V>> self,
+ Class<K> keyClass, Class<V> valueClass, Provider<V> valProvider) {
+ keyCodec = CodecFactory.encoder(keyClass);
+ valueCodec = CodecFactory.encoder(valueClass);
+ valueProvider = valProvider;
+ cache = self;
+
+ unpack = new Function<SerializableProtobuf<V>, V>() {
+ @Override
+ public V apply(SerializableProtobuf<V> val) {
+ return val != null ? val.toObject(valueCodec, valueProvider) : null;
+ }
+ };
+ }
+
+ @Override
+ public ListenableFuture<V> get(K key) {
+ return Futures.compose(cache.get(wrapKey(key)), unpack);
+ }
+
+ @Override
+ public ListenableFuture<Void> putAsync(final K key, final V value) {
+ return cache.putAsync(wrapKey(key), wrapValue(value));
+ }
+
+ @Override
+ public ListenableFuture<Void> removeAsync(final K key) {
+ if (key != null) {
+ return cache.removeAsync(wrapKey(key));
+ } else {
+ return Futures.immediateFuture(null);
+ }
+ }
+
+ @Override
+ public ListenableFuture<Void> removeAllAsync() {
+ return cache.removeAllAsync();
+ }
+
+ @Override
+ public long getTimeToLive(TimeUnit unit) {
+ return cache.getTimeToLive(unit);
+ }
+
+ @Override
+ public String toString() {
+ return cache.toString();
+ }
+
+ private SerializableProtobuf<K> wrapKey(K key) {
+ return new SerializableProtobuf<K>(key, keyCodec);
+ }
+
+ private SerializableProtobuf<V> wrapValue(final V value) {
+ return new SerializableProtobuf<V>(value, valueCodec);
+ }
+}
diff --git a/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/ProtobufEntryCreator.java b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/ProtobufEntryCreator.java
new file mode 100644
index 0000000..9feff06
--- /dev/null
+++ b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/ProtobufEntryCreator.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.ehcache;
+
+import com.google.gerrit.server.cache.EntryCreator;
+import com.google.gwtorm.protobuf.CodecFactory;
+import com.google.gwtorm.protobuf.ProtobufCodec;
+
+class ProtobufEntryCreator<K, V> extends
+ EntryCreator<SerializableProtobuf<K>, SerializableProtobuf<V>> {
+ private final ProtobufCodec<K> keyCodec;
+ private final ProtobufCodec<V> valueCodec;
+ private final EntryCreator<K, V> creator;
+
+ public ProtobufEntryCreator(EntryCreator<K, V> entryCreator,
+ Class<K> keyClass, Class<V> valueClass) {
+ this.keyCodec = CodecFactory.encoder(keyClass);
+ this.valueCodec = CodecFactory.encoder(valueClass);
+ this.creator = entryCreator;
+ }
+
+ @Override
+ public SerializableProtobuf<V> createEntry(SerializableProtobuf<K> key)
+ throws Exception {
+ return new SerializableProtobuf<V>(creator.createEntry(key.toObject(
+ keyCodec, null)), valueCodec);
+ }
+}
\ No newline at end of file
diff --git a/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/SerializableProtobuf.java b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/SerializableProtobuf.java
new file mode 100644
index 0000000..31a3b5e
--- /dev/null
+++ b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/SerializableProtobuf.java
@@ -0,0 +1,123 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.ehcache;
+
+import com.google.gwtorm.protobuf.ProtobufCodec;
+import com.google.inject.Provider;
+import com.google.protobuf.CodedOutputStream;
+
+import org.eclipse.jgit.util.IO;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.Arrays;
+
+class SerializableProtobuf<T> implements Serializable {
+ private static final long serialVersionUID = 100L;
+
+ private transient volatile Object data;
+ private transient ProtobufCodec<T> codec;
+ private transient int hash;
+
+ SerializableProtobuf(T object, ProtobufCodec<T> codec) {
+ this.data = object;
+ this.codec = codec;
+ this.hash = object.hashCode();
+ }
+
+ T toObject(ProtobufCodec<T> codec, Provider<T> provider) {
+ if (codec == null) {
+ return null;
+ }
+ Object d = data;
+ if (d instanceof byte[]) {
+ this.codec = codec;
+ if (provider == null) {
+ d = codec.decode((byte[]) d);
+ } else {
+ T tmp = provider.get();
+ codec.mergeFrom((byte[]) d, tmp);
+ d = tmp;
+ }
+ data = d;
+ }
+ return (T) d;
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof SerializableProtobuf<?>)) {
+ return false;
+ }
+ SerializableProtobuf<T> other = ((SerializableProtobuf<T>) obj);
+
+ if (hash != other.hash) {
+ return false;
+ }
+
+ // Make sure we either both have codecs, or we both do not
+ if (this.codec == null && other.codec != null) {
+ this.codec = other.codec;
+ } else if (this.codec != null && other.codec == null) {
+ other.codec = this.codec;
+ }
+
+ // Equals is only ever called on keys, which cannot have providers
+ T thisObject = this.toObject(codec, null);
+ T otherObject = other.toObject(other.codec, null);
+
+ if (thisObject == null && otherObject == null) {
+ // Neither of us had codecs, so we must compare byte arrays
+ return Arrays.equals((byte[]) this.data, (byte[]) other.data);
+ } else if (thisObject != null && otherObject != null) {
+ return thisObject.equals(otherObject);
+ } else {
+ return false;
+ }
+ }
+
+ private void writeObject(ObjectOutputStream oos) throws IOException {
+ oos.writeInt(hash);
+
+ Object d = data;
+ if (d instanceof byte[]) {
+ byte[] buf = (byte[]) d;
+ oos.writeInt(buf.length);
+ oos.write(buf);
+ } else {
+ // We assume that if we have an object, we must have a codec
+ T obj = (T) d;
+ oos.writeInt(codec.sizeof(obj));
+ CodedOutputStream cos = CodedOutputStream.newInstance(oos);
+ codec.encode(obj, cos);
+ cos.flush();
+ }
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException {
+ hash = in.readInt();
+ int len = in.readInt();
+ byte[] d = new byte[len];
+ IO.readFully(in, d, 0, len);
+ data = d;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/SimpleCache.java b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/SimpleCache.java
similarity index 74%
rename from gerrit-server/src/main/java/com/google/gerrit/server/cache/SimpleCache.java
rename to gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/SimpleCache.java
index 2283f96..f1d356d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/SimpleCache.java
+++ b/gerrit-ehcache/src/main/java/com/google/gerrit/ehcache/SimpleCache.java
@@ -12,10 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.cache;
+package com.google.gerrit.ehcache;
import static java.util.concurrent.TimeUnit.SECONDS;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.server.cache.Cache;
+
import net.sf.ehcache.CacheException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
@@ -45,10 +49,11 @@
}
@SuppressWarnings("unchecked")
- public V get(final K key) {
+ public ListenableFuture<V> get(final K key) {
if (key == null) {
- return null;
+ return Futures.immediateFuture(null);
}
+
final Element m;
try {
m = self.get(key);
@@ -59,21 +64,27 @@
log.error("Cannot lookup " + key + " in \"" + self.getName() + "\"", err);
return null;
}
- return m != null ? (V) m.getObjectValue() : null;
+ if (m != null) {
+ return Futures.immediateFuture((V) m.getObjectValue());
+ }
+ return Futures.immediateFuture(null);
}
- public void put(final K key, final V value) {
+ public ListenableFuture<Void> putAsync(final K key, final V value) {
self.put(new Element(key, value));
+ return Futures.immediateFuture(null);
}
- public void remove(final K key) {
+ public ListenableFuture<Void> removeAsync(final K key) {
if (key != null) {
self.remove(key);
}
+ return Futures.immediateFuture(null);
}
- public void removeAll() {
+ public ListenableFuture<Void> removeAllAsync() {
self.removeAll();
+ return Futures.immediateFuture(null);
}
@Override
diff --git a/gerrit-gwtdebug/pom.xml b/gerrit-gwtdebug/pom.xml
index 88246ac..d5a67e1 100644
--- a/gerrit-gwtdebug/pom.xml
+++ b/gerrit-gwtdebug/pom.xml
@@ -71,6 +71,12 @@
</dependency>
<dependency>
+ <groupId>com.google.protobuf</groupId>
+ <artifactId>protobuf-java</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
<groupId>bouncycastle</groupId>
<artifactId>bcprov-jdk15</artifactId>
<version>140</version>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index ec6b9ed..e25419f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -66,12 +66,12 @@
public static final GerritResources RESOURCES =
GWT.create(GerritResources.class);
public static final SystemInfoService SYSTEM_SVC;
- private static final String SESSION_COOKIE = "GerritAccount";
private static String myHost;
private static GerritConfig myConfig;
private static Account myAccount;
private static AccountDiffPreference myAccountDiffPref;
+ private static String xsrfToken;
private static TabPanel menuLeft;
private static LinkMenuBar menuRight;
@@ -212,10 +212,15 @@
}
static void deleteSessionCookie() {
- Cookies.removeCookie(SESSION_COOKIE);
myAccount = null;
myAccountDiffPref = null;
+ xsrfToken = null;
refreshMenuBar();
+
+ // If the cookie was HttpOnly, this request to delete it will
+ // most likely not be successful. We can try anyway though.
+ //
+ Cookies.removeCookie("GerritAccount");
}
public void onModuleLoad() {
@@ -251,6 +256,7 @@
myConfig = result.config;
if (result.account != null) {
myAccount = result.account;
+ xsrfToken = result.xsrfToken;
}
if (result.accountDiffPref != null) {
myAccountDiffPref = result.accountDiffPref;
@@ -377,7 +383,7 @@
JsonUtil.setDefaultXsrfManager(new XsrfManager() {
@Override
public String getToken(JsonDefTarget proxy) {
- return Cookies.getCookie(SESSION_COOKIE);
+ return xsrfToken;
}
@Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java
index 8c99d45..605d7f3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java
@@ -99,18 +99,20 @@
formBody.add(fp);
}
- final FlowPanel sshKeyGroup = new FlowPanel();
- sshKeyGroup.setStyleName(Gerrit.RESOURCES.css().registerScreenSection());
- sshKeyGroup.add(new SmallHeading(Util.C.welcomeSshKeyHeading()));
- final HTML whySshKey = new HTML(Util.C.welcomeSshKeyText());
- whySshKey.setStyleName(Gerrit.RESOURCES.css().registerScreenExplain());
- sshKeyGroup.add(whySshKey);
- sshKeyGroup.add(new SshPanel() {
- {
- setKeyTableVisible(false);
- }
- });
- formBody.add(sshKeyGroup);
+ if (Gerrit.getConfig().getSshdAddress() != null) {
+ final FlowPanel sshKeyGroup = new FlowPanel();
+ sshKeyGroup.setStyleName(Gerrit.RESOURCES.css().registerScreenSection());
+ sshKeyGroup.add(new SmallHeading(Util.C.welcomeSshKeyHeading()));
+ final HTML whySshKey = new HTML(Util.C.welcomeSshKeyText());
+ whySshKey.setStyleName(Gerrit.RESOURCES.css().registerScreenExplain());
+ sshKeyGroup.add(whySshKey);
+ sshKeyGroup.add(new SshPanel() {
+ {
+ setKeyTableVisible(false);
+ }
+ });
+ formBody.add(sshKeyGroup);
+ }
final FlowPanel choices = new FlowPanel();
choices.setStyleName(Gerrit.RESOURCES.css().registerScreenNextLinks());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
index 9356018..97b2efb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
@@ -26,7 +26,9 @@
link(Util.C.tabPreferences(), PageLinks.SETTINGS_PREFERENCES);
link(Util.C.tabWatchedProjects(), PageLinks.SETTINGS_PROJECTS);
link(Util.C.tabContactInformation(), PageLinks.SETTINGS_CONTACT);
- link(Util.C.tabSshKeys(), PageLinks.SETTINGS_SSHKEYS);
+ if (Gerrit.getConfig().getSshdAddress() != null) {
+ link(Util.C.tabSshKeys(), PageLinks.SETTINGS_SSHKEYS);
+ }
link(Util.C.tabHttpAccess(), PageLinks.SETTINGS_HTTP_PASSWORD);
link(Util.C.tabWebIdentities(), PageLinks.SETTINGS_WEBIDENT);
link(Util.C.tabMyGroups(), PageLinks.SETTINGS_MYGROUPS);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
index f67f12f..2118b9c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectDigestFilter.java
@@ -20,9 +20,10 @@
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
+import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtjsonrpc.server.SignedToken;
import com.google.gwtjsonrpc.server.XsrfException;
import com.google.inject.Inject;
@@ -132,18 +133,24 @@
return false;
}
- final AccountState who = accountCache.getByUsername(username);
- if (who == null || ! who.getAccount().isActive()) {
+ final AccountExternalId who = FutureUtil.get( //
+ accountCache.get(AccountExternalId.forUsername(username)));
+ if (who == null) {
rsp.sendError(SC_UNAUTHORIZED);
return false;
}
- final String passwd = who.getPassword(username);
+ final String passwd = who.getPassword();
if (passwd == null) {
rsp.sendError(SC_UNAUTHORIZED);
return false;
}
+ if (!FutureUtil.get(accountCache.getAccount(who.getAccountId())).isActive()) {
+ rsp.sendError(SC_UNAUTHORIZED);
+ return false;
+ }
+
final String A1 = username + ":" + realm + ":" + passwd;
final String A2 = method + ":" + uri;
@@ -158,7 +165,7 @@
if (expect.equals(response)) {
try {
if (tokens.checkToken(nonce, "") != null) {
- session.get().setUserAccountId(who.getAccount().getId());
+ session.get().setUserAccountId(who.getAccountId());
return true;
} else {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/SessionCacheCleaner.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/SessionCacheCleaner.java
new file mode 100644
index 0000000..f0ff5d6
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/SessionCacheCleaner.java
@@ -0,0 +1,123 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd;
+
+import static java.util.concurrent.TimeUnit.HOURS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import com.google.gerrit.lifecycle.LifecycleListener;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.reviewdb.ActiveSession;
+import com.google.gerrit.reviewdb.ActiveSessionAccess;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.reviewdb.ActiveSession.Key;
+import com.google.gerrit.server.cache.Cache;
+import com.google.gerrit.server.git.WorkQueue;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Timestamp;
+import java.util.LinkedList;
+import java.util.List;
+
+/** Removes expired sessions from the session database cache */
+public class SessionCacheCleaner implements Runnable {
+ private static final Logger log =
+ LoggerFactory.getLogger(SessionCacheCleaner.class);
+
+ public static class Module extends LifecycleModule {
+ @Override
+ protected void configure() {
+ listener().to(Lifecycle.class);
+ }
+ }
+
+ static class Lifecycle implements LifecycleListener {
+ private final WorkQueue queue;
+ private final SessionCacheCleaner cleaner;
+
+ @Inject
+ Lifecycle(final WorkQueue queue, final SessionCacheCleaner cleaner) {
+ this.queue = queue;
+ this.cleaner = cleaner;
+ }
+
+ @Override
+ public void start() {
+ queue.getDefaultQueue().scheduleWithFixedDelay(cleaner, 1, 12, HOURS);
+ }
+
+ @Override
+ public void stop() {
+ }
+ }
+
+ private final SchemaFactory<ReviewDb> reviewDbFactory;
+ private final Cache<Key, ActiveSession> cache;
+
+ @Inject
+ public SessionCacheCleaner(
+ SchemaFactory<ReviewDb> reviewDbFactory,
+ @Named(WebSession.CACHE_NAME) final Cache<ActiveSession.Key, ActiveSession> cache) {
+ this.reviewDbFactory = reviewDbFactory;
+ this.cache = cache;
+ }
+
+ @Override
+ public void run() {
+ try {
+ ReviewDb db = reviewDbFactory.open();
+ try {
+ final ActiveSessionAccess access = db.activeSessions();
+ final List<ActiveSession> expiredSessions =
+ new LinkedList<ActiveSession>();
+ final List<ActiveSession> activeSessions = access.all().toList();
+ final Timestamp now = new Timestamp(System.currentTimeMillis());
+
+ for (ActiveSession as : activeSessions) {
+ if (expiredFromCache(as, now)) {
+ expiredSessions.add(as);
+ }
+ }
+
+ try {
+ access.delete(expiredSessions);
+ } catch (OrmException e) {
+ log.error("Unable to delete expired sessions from database", e);
+ }
+ } finally {
+ db.close();
+ }
+ } catch (OrmException e) {
+ log.error("Unable to fetch sessions from database", e);
+ }
+ }
+
+ private boolean expiredFromCache(ActiveSession as, Timestamp now) {
+ if (as.getLastSeen() == null) {
+ return true;
+ }
+ final Timestamp expireAt =
+ new Timestamp(as.getLastSeen().getTime()
+ + cache.getTimeToLive(MILLISECONDS));
+
+ return now.after(expireAt);
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index 93b6d09..bf96326 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -37,12 +37,9 @@
import com.google.gerrit.server.config.GerritRequestModule;
import com.google.gerrit.server.contact.ContactStore;
import com.google.gerrit.server.contact.ContactStoreProvider;
-import com.google.gerrit.server.ssh.SshInfo;
-import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
-import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.servlet.RequestScoped;
import com.google.inject.servlet.ServletModule;
@@ -52,20 +49,14 @@
import javax.annotation.Nullable;
public class WebModule extends FactoryModule {
- private final Provider<SshInfo> sshInfoProvider;
- private final Provider<SshKeyCache> sshKeyCacheProvider;
private final AuthType authType;
private final boolean wantSSL;
private final GitWebConfig gitWebConfig;
@Inject
- WebModule(final Provider<SshInfo> sshInfoProvider,
- final Provider<SshKeyCache> sshKeyCacheProvider,
- final AuthConfig authConfig,
+ WebModule(final AuthConfig authConfig,
@CanonicalWebUrl @Nullable final String canonicalUrl,
final Injector creatingInjector) {
- this.sshInfoProvider = sshInfoProvider;
- this.sshKeyCacheProvider = sshKeyCacheProvider;
this.authType = authConfig.getAuthType();
this.wantSSL = canonicalUrl != null && canonicalUrl.startsWith("https:");
@@ -124,9 +115,6 @@
install(new GerritRequestModule());
install(new ProjectServlet.Module());
- bind(SshInfo.class).toProvider(sshInfoProvider);
- bind(SshKeyCache.class).toProvider(sshKeyCacheProvider);
-
bind(GitWebConfig.class).toInstance(gitWebConfig);
if (gitWebConfig.getGitwebCGI() != null) {
install(new GitWebModule());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java
index 90ccdc5..151d7aa 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSession.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,12 +14,17 @@
package com.google.gerrit.httpd;
+import static com.google.gerrit.server.ioutil.BasicSerialization.writeBytes;
+import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
+import static com.google.inject.Scopes.SINGLETON;
import static java.util.concurrent.TimeUnit.HOURS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
-import com.google.gerrit.httpd.WebSessionManager.Key;
-import com.google.gerrit.httpd.WebSessionManager.Val;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
+import com.google.gerrit.reviewdb.ActiveSession;
+import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
@@ -28,76 +33,109 @@
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.EvictionPolicy;
+import com.google.gerrit.server.util.FutureUtil;
+import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Module;
+import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
import com.google.inject.servlet.RequestScoped;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RequestScoped
public final class WebSession {
+ private static final Logger log = LoggerFactory.getLogger(WebSession.class);
private static final String ACCOUNT_COOKIE = "GerritAccount";
+ static final String CACHE_NAME = "web_sessions";
+ private static final long UPDATE_WAIT_MILLISECONDS =
+ MILLISECONDS.convert(5, TimeUnit.MINUTES);
static Module module() {
return new CacheModule() {
@Override
protected void configure() {
- final String cacheName = WebSessionManager.CACHE_NAME;
- final TypeLiteral<Cache<Key, Val>> type =
- new TypeLiteral<Cache<Key, Val>>() {};
- disk(type, cacheName) //
+ final TypeLiteral<Cache<ActiveSession.Key, ActiveSession>> type =
+ new TypeLiteral<Cache<ActiveSession.Key, ActiveSession>>() {};
+ cache(type, CACHE_NAME) //
.memoryLimit(1024) // reasonable default for many sites
.maxAge(12, HOURS) // expire sessions if they are inactive
.evictionPolicy(EvictionPolicy.LRU) // keep most recently used
;
- bind(WebSessionManager.class);
bind(WebSession.class).in(RequestScoped.class);
+ bind(WebSession.KeyGenerator.class).in(SINGLETON);
}
};
}
+ // We want a singleton SecureRandom, but we don't want to make every
+ // SecureRandom a singleton, so instead we have a KeyGenerator class that can
+ // be used in its place.
+ @Singleton
+ private static class KeyGenerator extends SecureRandom {
+ }
+
+ private static long now() {
+ return System.currentTimeMillis();
+ }
+
+ private final KeyGenerator prng;
+ private final Cache<ActiveSession.Key, ActiveSession> cache;
private final HttpServletRequest request;
private final HttpServletResponse response;
- private final WebSessionManager manager;
private final AnonymousUser anonymous;
private final IdentifiedUser.RequestFactory identified;
+ private final ReviewDb schema;
private AccessPath accessPath = AccessPath.WEB_UI;
private Cookie outCookie;
- private Key key;
- private Val val;
+ private ActiveSession.Key key;
+ private ActiveSession session;
@Inject
WebSession(final HttpServletRequest request,
- final HttpServletResponse response, final WebSessionManager manager,
- final AnonymousUser anonymous,
- final IdentifiedUser.RequestFactory identified) {
+ final HttpServletResponse response, final AnonymousUser anonymous,
+ final IdentifiedUser.RequestFactory identified, ReviewDb schema,
+ @Named(CACHE_NAME) final Cache<ActiveSession.Key, ActiveSession> cache,
+ KeyGenerator prng) throws OrmException {
this.request = request;
this.response = response;
- this.manager = manager;
this.anonymous = anonymous;
this.identified = identified;
+ this.schema = schema;
+ this.cache = cache;
+ this.prng = prng;
final String cookie = readCookie();
if (cookie != null) {
- key = new Key(cookie);
- val = manager.get(key);
+ key = new ActiveSession.Key(cookie);
+ session = get(key);
} else {
key = null;
- val = null;
+ session = null;
}
- if (isSignedIn() && val.needsCookieRefresh()) {
+ if (isSignedIn() && session.needsCookieRefresh()) {
// Cookie is more than half old. Send the cookie again to the
// client with an updated expiration date. We don't dare to
// change the key token here because there may be other RPCs
// queued up in the browser whose xsrfKey would not get updated
// with the new token, causing them to fail.
//
- val = manager.createVal(key, val);
+ session = createSession(key, session);
saveCookie();
}
}
@@ -116,36 +154,43 @@
}
public boolean isSignedIn() {
- return val != null;
+ return session != null;
}
- String getToken() {
- return isSignedIn() ? key.getToken() : null;
+ public String getToken() {
+ return isSignedIn() ? session.getXsrfToken() : null;
}
public boolean isTokenValid(final String inputToken) {
- return isSignedIn() && key.getToken().equals(inputToken);
+ return isSignedIn() //
+ && session.getXsrfToken() != null //
+ && session.getXsrfToken().equals(inputToken);
}
public AccountExternalId.Key getLastLoginExternalId() {
- return val != null ? val.getExternalId() : null;
+ return session != null ? session.getExternalId() : null;
}
CurrentUser getCurrentUser() {
if (isSignedIn()) {
- return identified.create(accessPath, val.getAccountId());
+ return identified.create(accessPath, session.getAccountId());
}
return anonymous;
}
- public void login(final AuthResult res, final boolean rememberMe) {
+ public void login(final AuthResult res, final boolean rememberMe)
+ throws OrmException {
final Account.Id id = res.getAccountId();
final AccountExternalId.Key identity = res.getExternalId();
- logout();
+ if (session != null) {
+ destroy(key);
+ key = null;
+ session = null;
+ }
- key = manager.createKey(id);
- val = manager.createVal(key, id, rememberMe, identity);
+ key = createKey(id);
+ session = createSession(key, id, rememberMe, identity, null);
saveCookie();
}
@@ -156,15 +201,19 @@
/** Set the user account for this current request only. */
void setUserAccountId(Account.Id id) {
- key = new Key("id:" + id);
- val = new Val(id, 0, false, null);
+ key = new ActiveSession.Key("id:" + id);
+ session = new ActiveSession(key, id, new Timestamp(0), false, null, "");
}
public void logout() {
- if (val != null) {
- manager.destroy(key);
+ if (session != null) {
+ try {
+ destroy(key);
+ } catch (OrmException e) {
+ log.error("Could not remove session key from cache", e);
+ }
key = null;
- val = null;
+ session = null;
saveCookie();
}
}
@@ -177,22 +226,160 @@
token = "";
ageSeconds = 0 /* erase at client */;
} else {
- token = key.getToken();
- ageSeconds = manager.getCookieAge(val);
+ token = key.get();
+ ageSeconds = getCookieAge(session);
}
- if (outCookie == null) {
- String path = request.getContextPath();
- if (path.equals("")) {
- path = "/";
- }
- outCookie = new Cookie(ACCOUNT_COOKIE, token);
- outCookie.setPath(path);
- outCookie.setMaxAge(ageSeconds);
- response.addCookie(outCookie);
- } else {
- outCookie.setValue(token);
- outCookie.setMaxAge(ageSeconds);
+ String path = request.getContextPath();
+ if (path.equals("")) {
+ path = "/";
}
+
+ if (outCookie != null) {
+ throw new IllegalStateException("Cookie " + ACCOUNT_COOKIE + " was set");
+ }
+
+ outCookie = new Cookie(ACCOUNT_COOKIE, token);
+ outCookie.setSecure(isSecure(request));
+ outCookie.setPath(path);
+ outCookie.setMaxAge(ageSeconds);
+ response.addCookie(outCookie);
+ }
+
+ private static boolean isSecure(final HttpServletRequest req) {
+ return req.isSecure() || "https".equals(req.getScheme());
+ }
+
+ private ActiveSession.Key createKey(final Account.Id who) {
+ try {
+ final int nonceLen = 20;
+ final ByteArrayOutputStream buf;
+ final byte[] rnd = new byte[nonceLen];
+ prng.nextBytes(rnd);
+
+ buf = new ByteArrayOutputStream(3 + nonceLen);
+ writeVarInt32(buf, who.get());
+ writeBytes(buf, rnd);
+
+ return new ActiveSession.Key(CookieBase64.encode(buf.toByteArray()));
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot produce new account cookie", e);
+ }
+ }
+
+ private ActiveSession createSession(final ActiveSession.Key key,
+ final ActiveSession session) throws OrmException {
+ final Account.Id who = session.getAccountId();
+ final boolean remember = session.isPersistentCookie();
+ final AccountExternalId.Key lastLogin = session.getExternalId();
+ final String xsrfToken = session.getXsrfToken();
+
+ return createSession(key, who, remember, lastLogin, xsrfToken);
+ }
+
+ private ActiveSession createSession(final ActiveSession.Key key,
+ final Account.Id who, final boolean remember,
+ final AccountExternalId.Key lastLogin, String xsrfToken)
+ throws OrmException {
+ // Refresh the cookie every hour or when it is half-expired.
+ // This reduces the odds that the user session will be kicked
+ // early but also avoids us needing to refresh the cookie on
+ // every single request.
+ //
+ final long halfAgeRefresh = cache.getTimeToLive(MILLISECONDS) >>> 1;
+ final long minRefresh = MILLISECONDS.convert(1, HOURS);
+ final long refresh = Math.min(halfAgeRefresh, minRefresh);
+ final long refreshCookieAt = now() + refresh;
+
+ if (xsrfToken == null) {
+ // If we don't yet have a token for this session, establish one.
+ //
+ final int nonceLen = 20;
+ final ByteArrayOutputStream buf;
+ final byte[] rnd = new byte[nonceLen];
+ prng.nextBytes(rnd);
+ xsrfToken = CookieBase64.encode(rnd);
+ }
+
+ ActiveSession session =
+ new ActiveSession(key, who, new Timestamp(refreshCookieAt), remember,
+ lastLogin, xsrfToken);
+ put(session);
+ return session;
+ }
+
+ private int getCookieAge(final ActiveSession session) {
+ if (session.isPersistentCookie()) {
+ // Client may store the cookie until we would remove it from our
+ // own cache, after which it will certainly be invalid.
+ //
+ return (int) cache.getTimeToLive(SECONDS);
+ } else {
+ // Client should not store the cookie, as the user asked for us
+ // to not remember them long-term. Sending -1 as the age will
+ // cause the cookie to be only for this "browser session", which
+ // is usually until the user exits their browser.
+ //
+ return -1;
+ }
+ }
+
+ private ActiveSession get(final ActiveSession.Key key) throws OrmException {
+ ActiveSession as = FutureUtil.get(cache.get(key));
+ if (as == null) {
+ as = schema.activeSessions().get(key);
+
+ if (as == null) {
+ return null;
+ } else {
+ if (expiredFromCache(as)) {
+ destroy(key);
+ return null;
+ } else if (needsCacheRefresh(as)) {
+ as.updateLastSeen();
+ put(as);
+ }
+ return as;
+ }
+ } else {
+ if (needsCacheRefresh(as)) {
+ as.updateLastSeen();
+ put(as);
+ }
+ return as;
+ }
+ }
+
+ private boolean needsCacheRefresh(ActiveSession as) {
+ if (as.getLastSeen() == null) {
+ return true;
+ }
+ Timestamp refreshAt =
+ new Timestamp(as.getLastSeen().getTime() + UPDATE_WAIT_MILLISECONDS);
+ Timestamp now = new Timestamp(now());
+
+ return now.after(refreshAt);
+ }
+
+ private boolean expiredFromCache(ActiveSession as) {
+ if (as.getLastSeen() == null) {
+ return true;
+ }
+ Timestamp expireAt =
+ new Timestamp(as.getLastSeen().getTime()
+ + cache.getTimeToLive(MILLISECONDS));
+ Timestamp now = new Timestamp(now());
+
+ return now.after(expireAt);
+ }
+
+ private void destroy(final ActiveSession.Key key) throws OrmException {
+ schema.activeSessions().deleteKeys(Arrays.asList(key));
+ FutureUtil.waitFor(cache.removeAsync(key));
+ }
+
+ private void put(final ActiveSession as) throws OrmException {
+ schema.activeSessions().upsert(Arrays.asList(as));
+ FutureUtil.waitFor(cache.putAsync(as.getKey(), as));
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java
deleted file mode 100644
index 43c99ad..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSessionManager.java
+++ /dev/null
@@ -1,232 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.httpd;
-
-import static com.google.gerrit.server.ioutil.BasicSerialization.readFixInt64;
-import static com.google.gerrit.server.ioutil.BasicSerialization.readString;
-import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32;
-import static com.google.gerrit.server.ioutil.BasicSerialization.writeBytes;
-import static com.google.gerrit.server.ioutil.BasicSerialization.writeFixInt64;
-import static com.google.gerrit.server.ioutil.BasicSerialization.writeString;
-import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
-import static java.util.concurrent.TimeUnit.HOURS;
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.reviewdb.AccountExternalId;
-import com.google.gerrit.server.cache.Cache;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import com.google.inject.name.Named;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.security.SecureRandom;
-
-@Singleton
-class WebSessionManager {
- static final String CACHE_NAME = "web_sessions";
-
- static long now() {
- return System.currentTimeMillis();
- }
-
- private final SecureRandom prng;
- private final Cache<Key, Val> self;
-
- @Inject
- WebSessionManager(@Named(CACHE_NAME) final Cache<Key, Val> cache) {
- prng = new SecureRandom();
- self = cache;
- }
-
- Key createKey(final Account.Id who) {
- try {
- final int nonceLen = 20;
- final ByteArrayOutputStream buf;
- final byte[] rnd = new byte[nonceLen];
- prng.nextBytes(rnd);
-
- buf = new ByteArrayOutputStream(3 + nonceLen);
- writeVarInt32(buf, (int) Key.serialVersionUID);
- writeVarInt32(buf, who.get());
- writeBytes(buf, rnd);
-
- return new Key(CookieBase64.encode(buf.toByteArray()));
- } catch (IOException e) {
- throw new RuntimeException("Cannot produce new account cookie", e);
- }
- }
-
- Val createVal(final Key key, final Val val) {
- final Account.Id who = val.getAccountId();
- final boolean remember = val.isPersistentCookie();
- final AccountExternalId.Key lastLogin = val.getExternalId();
-
- return createVal(key, who, remember, lastLogin);
- }
-
- Val createVal(final Key key, final Account.Id who, final boolean remember,
- final AccountExternalId.Key lastLogin) {
- // Refresh the cookie every hour or when it is half-expired.
- // This reduces the odds that the user session will be kicked
- // early but also avoids us needing to refresh the cookie on
- // every single request.
- //
- final long halfAgeRefresh = self.getTimeToLive(MILLISECONDS) >>> 1;
- final long minRefresh = MILLISECONDS.convert(1, HOURS);
- final long refresh = Math.min(halfAgeRefresh, minRefresh);
- final long refreshCookieAt = now() + refresh;
-
- final Val val = new Val(who, refreshCookieAt, remember, lastLogin);
- self.put(key, val);
- return val;
- }
-
- int getCookieAge(final Val val) {
- if (val.isPersistentCookie()) {
- // Client may store the cookie until we would remove it from our
- // own cache, after which it will certainly be invalid.
- //
- return (int) self.getTimeToLive(SECONDS);
- } else {
- // Client should not store the cookie, as the user asked for us
- // to not remember them long-term. Sending -1 as the age will
- // cause the cookie to be only for this "browser session", which
- // is usually until the user exits their browser.
- //
- return -1;
- }
- }
-
- Val get(final Key key) {
- return self.get(key);
- }
-
- void destroy(final Key key) {
- self.remove(key);
- }
-
- static final class Key implements Serializable {
- static final long serialVersionUID = 2L;
-
- private transient String token;
-
- Key(final String t) {
- token = t;
- }
-
- String getToken() {
- return token;
- }
-
- @Override
- public int hashCode() {
- return token.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- return obj instanceof Key && token.equals(((Key) obj).token);
- }
-
- private void writeObject(final ObjectOutputStream out) throws IOException {
- writeString(out, token);
- }
-
- private void readObject(final ObjectInputStream in) throws IOException {
- token = readString(in);
- }
- }
-
- static final class Val implements Serializable {
- static final long serialVersionUID = Key.serialVersionUID;
-
- private transient Account.Id accountId;
- private transient long refreshCookieAt;
- private transient boolean persistentCookie;
- private transient AccountExternalId.Key externalId;
-
- Val(final Account.Id accountId, final long refreshCookieAt,
- final boolean persistentCookie, final AccountExternalId.Key externalId) {
- this.accountId = accountId;
- this.refreshCookieAt = refreshCookieAt;
- this.persistentCookie = persistentCookie;
- this.externalId = externalId;
- }
-
- Account.Id getAccountId() {
- return accountId;
- }
-
- AccountExternalId.Key getExternalId() {
- return externalId;
- }
-
- boolean needsCookieRefresh() {
- return refreshCookieAt <= now();
- }
-
- boolean isPersistentCookie() {
- return persistentCookie;
- }
-
- private void writeObject(final ObjectOutputStream out) throws IOException {
- writeVarInt32(out, 1);
- writeVarInt32(out, accountId.get());
-
- writeVarInt32(out, 2);
- writeFixInt64(out, refreshCookieAt);
-
- writeVarInt32(out, 3);
- writeVarInt32(out, persistentCookie ? 1 : 0);
-
- if (externalId != null) {
- writeVarInt32(out, 4);
- writeString(out, externalId.get());
- }
-
- writeVarInt32(out, 0);
- }
-
- private void readObject(final ObjectInputStream in) throws IOException {
- PARSE: for (;;) {
- final int tag = readVarInt32(in);
- switch (tag) {
- case 0:
- break PARSE;
- case 1:
- accountId = new Account.Id(readVarInt32(in));
- continue;
- case 2:
- refreshCookieAt = readFixInt64(in);
- continue;
- case 3:
- persistentCookie = readVarInt32(in) != 0;
- continue;
- case 4:
- externalId = new AccountExternalId.Key(readString(in));
- continue;
- default:
- throw new IOException("Unknown tag found in object: " + tag);
- }
- }
- }
- }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSshGlueModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSshGlueModule.java
new file mode 100644
index 0000000..0de74b1
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebSshGlueModule.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.httpd;
+
+import com.google.gerrit.server.ssh.SshInfo;
+import com.google.gerrit.server.ssh.SshKeyCache;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+/**
+ * Pulls objects from the SSH injector over the HTTP injector.
+ * <p>
+ * This mess is only necessary because we build up two different injectors, in
+ * order to have different request scopes. But some HTTP RPCs can cause changes
+ * to the SSH side of the house, and thus needs access to it.
+ */
+public class WebSshGlueModule extends AbstractModule {
+ private final Provider<SshInfo> sshInfoProvider;
+ private final Provider<SshKeyCache> sshKeyCacheProvider;
+
+ @Inject
+ WebSshGlueModule(Provider<SshInfo> sshInfoProvider,
+ Provider<SshKeyCache> sshKeyCacheProvider) {
+ this.sshInfoProvider = sshInfoProvider;
+ this.sshKeyCacheProvider = sshKeyCacheProvider;
+ }
+
+ @Override
+ protected void configure() {
+ bind(SshInfo.class).toProvider(sshInfoProvider);
+ bind(SshKeyCache.class).toProvider(sshKeyCacheProvider);
+
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index c3f7de1..915e925 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -14,19 +14,19 @@
package com.google.gerrit.httpd.auth.become;
-import static com.google.gerrit.reviewdb.AccountExternalId.SCHEME_USERNAME;
-
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult;
import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
@@ -58,18 +58,20 @@
private final Provider<WebSession> webSession;
private final Provider<String> urlProvider;
private final AccountManager accountManager;
+ private final AccountCache accountCache;
private final byte[] raw;
@Inject
BecomeAnyAccountLoginServlet(final Provider<WebSession> ws,
final SchemaFactory<ReviewDb> sf,
final @CanonicalWebUrl @Nullable Provider<String> up,
- final AccountManager am, final ServletContext servletContext)
- throws IOException {
+ final AccountManager am, final ServletContext servletContext,
+ final AccountCache ac) throws IOException {
webSession = ws;
schema = sf;
urlProvider = up;
accountManager = am;
+ accountCache = ac;
final String pageName = "BecomeAnyAccount.html";
final Document doc = HtmlDomUtil.parseFile(getClass(), pageName);
@@ -125,7 +127,21 @@
}
if (res != null) {
- webSession.get().login(res, false);
+ try {
+ webSession.get().login(res, false);
+ } catch (OrmException e) {
+ rsp.setContentType("text/html");
+ rsp.setCharacterEncoding(HtmlDomUtil.ENC);
+ final Writer out = rsp.getWriter();
+ out.write("<html>");
+ out.write("<body>");
+ out.write("<h1>Could not log in</h1>");
+ out.write("</body>");
+ out.write("</html>");
+ out.close();
+ log("Could not log in", e);
+ return;
+ }
final StringBuilder rdr = new StringBuilder();
rdr.append(urlProvider.get());
if (IS_DEV && req.getParameter("gwt.codesvr") != null) {
@@ -173,19 +189,8 @@
private AuthResult byUserName(final HttpServletResponse rsp,
final String userName) {
- try {
- final ReviewDb db = schema.open();
- try {
- AccountExternalId.Key key =
- new AccountExternalId.Key(SCHEME_USERNAME, userName);
- return auth(db.accountExternalIds().get(key));
- } finally {
- db.close();
- }
- } catch (OrmException e) {
- getServletContext().log("cannot query database", e);
- return null;
- }
+ AccountExternalId.Key key = AccountExternalId.forUsername(userName);
+ return auth(FutureUtil.get(accountCache.get(key)));
}
private AuthResult byPreferredEmail(final HttpServletResponse rsp,
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
index 58f589a..b3e1840 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/container/HttpLoginServlet.java
@@ -23,6 +23,7 @@
import com.google.gerrit.server.account.AuthResult;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -36,6 +37,7 @@
import org.w3c.dom.NodeList;
import java.io.IOException;
+import java.io.Writer;
import javax.annotation.Nullable;
import javax.servlet.ServletException;
@@ -136,7 +138,21 @@
}
rdr.append(token);
- webSession.get().login(arsp, true /* persistent cookie */);
+ try {
+ webSession.get().login(arsp, true /* persistent cookie */);
+ } catch (OrmException e) {
+ log.error("Unable to log in user \"" + user + "\"", e);
+ rsp.setContentType("text/html");
+ rsp.setCharacterEncoding(HtmlDomUtil.ENC);
+ final Writer out = rsp.getWriter();
+ out.write("<html>");
+ out.write("<body>");
+ out.write("<h1>An error occurred while attempting to log in</h1>");
+ out.write("</body>");
+ out.write("</html>");
+ out.close();
+ return;
+ }
rsp.sendRedirect(rdr.toString());
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java
index a59d013..c8c05ce 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/ldap/UserPassAuthServiceImpl.java
@@ -22,12 +22,18 @@
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult;
import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
class UserPassAuthServiceImpl implements UserPassAuthService {
private final Provider<WebSession> webSession;
private final AccountManager accountManager;
+ private final Logger log =
+ LoggerFactory.getLogger(UserPassAuthServiceImpl.class);
@Inject
UserPassAuthServiceImpl(final Provider<WebSession> webSession,
@@ -63,7 +69,14 @@
result.success = true;
result.isNew = res.isNew();
- webSession.get().login(res, true /* persistent cookie */);
+ try {
+ webSession.get().login(res, true /* persistent cookie */);
+ } catch (OrmException e) {
+ log.error("Unable to log in", e);
+ result.success = false;
+ callback.onSuccess(result);
+ return;
+ }
callback.onSuccess(result);
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
index 068855f..4b5e2fe 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/openid/OpenIdServiceImpl.java
@@ -32,6 +32,7 @@
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtorm.client.KeyUtil;
+import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -415,7 +416,13 @@
lastId.setMaxAge(0);
}
rsp.addCookie(lastId);
- webSession.get().login(arsp, remember);
+ try {
+ webSession.get().login(arsp, remember);
+ } catch (OrmException e) {
+ log.error("Unable to log in",e);
+ cancelWithError(req, rsp, "Cannot start new session");
+ return;
+ }
if (arsp.isNew() && claimedIdentifier != null) {
final com.google.gerrit.server.account.AuthRequest linkReq =
new com.google.gerrit.server.account.AuthRequest(
@@ -429,7 +436,11 @@
case LINK_IDENTIY: {
arsp = accountManager.link(identifiedUser.get().getAccountId(), areq);
- webSession.get().login(arsp, remember);
+ try {
+ webSession.get().login(arsp, remember);
+ } catch (OrmException e) {
+ log.error("Unable to log in",e);
+ }
callback(false, req, rsp);
break;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
index 4521348..176a71f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
@@ -17,6 +17,7 @@
import com.google.gerrit.common.data.GerritConfig;
import com.google.gerrit.common.data.HostPageData;
import com.google.gerrit.httpd.HtmlDomUtil;
+import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.SitePaths;
@@ -61,6 +62,7 @@
private static final String HPD_ID = "gerrit_hostpagedata";
private final Provider<CurrentUser> currentUser;
+ private final Provider<WebSession> session;
private final GerritConfig config;
private final SitePaths site;
private final Document template;
@@ -69,10 +71,11 @@
private volatile Page page;
@Inject
- HostPageServlet(final Provider<CurrentUser> cu, final SitePaths sp,
- final GerritConfig gc, final ServletContext servletContext)
- throws IOException, ServletException {
+ HostPageServlet(final Provider<CurrentUser> cu, final Provider<WebSession> w,
+ final SitePaths sp, final GerritConfig gc,
+ final ServletContext servletContext) throws IOException, ServletException {
currentUser = cu;
+ session = w;
config = gc;
site = sp;
@@ -158,10 +161,17 @@
w.write(HPD_ID + ".account=");
json(((IdentifiedUser) user).getAccount(), w);
w.write(";");
+
w.write(HPD_ID + ".accountDiffPref=");
json(((IdentifiedUser) user).getAccountDiffPreference(), w);
w.write(";");
+ if (session.get().getToken() != null) {
+ w.write(HPD_ID + ".xsrfToken=");
+ json(session.get().getToken(), w);
+ w.write(";");
+ }
+
final byte[] userData = w.toString().getBytes("UTF-8");
raw = concat(page.part1, userData, page.part2);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
index 332e262..88769ad 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/ChangeListServiceImpl.java
@@ -27,6 +27,7 @@
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.StarredChange;
+import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.project.ChangeControl;
@@ -354,7 +355,7 @@
private abstract class QueryPrev extends QueryNext {
QueryPrev(int pageSize, String pos) {
- super(pageSize, pos);
+ super(pageSize, ChangeUtil.invertSortKey(pos));
}
@Override
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
index 8f7754a..d23ef5d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
@@ -52,8 +52,8 @@
protected GsonBuilder createGsonBuilder() {
final GsonBuilder g = super.createGsonBuilder();
- g.registerTypeAdapter(org.eclipse.jgit.diff.Edit.class,
- new org.eclipse.jgit.diff.EditDeserializer());
+ g.registerTypeAdapter(com.google.gerrit.prettify.common.LineEdit.class,
+ new com.google.gerrit.prettify.server.EditDeserializer());
return g;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
index 864c550..4595520 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/SuggestServiceImpl.java
@@ -14,6 +14,8 @@
package com.google.gerrit.httpd.rpc;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.common.data.SuggestService;
import com.google.gerrit.reviewdb.Account;
@@ -25,6 +27,7 @@
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
@@ -34,6 +37,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Future;
class SuggestServiceImpl extends BaseServiceImplementation implements
SuggestService {
@@ -62,15 +66,20 @@
final int max = 10;
final int n = limit <= 0 ? max : Math.min(limit, max);
- final CurrentUser user = currentUser.get();
- final List<Project.NameKey> r = new ArrayList<Project.NameKey>();
- for (final Project p : db.projects().suggestByName(a, b, n)) {
- final ProjectState e = projectCache.get(p.getNameKey());
+ List<Future<ProjectState>> want = Lists.newArrayList();
+ for (Project p : db.projects().suggestByName(a, b, n)) {
+ want.add(projectCache.get(p.getNameKey()));
+ }
+
+ CurrentUser user = currentUser.get();
+ List<Project.NameKey> res = Lists.newArrayList();
+ for (Future<ProjectState> f : want) {
+ ProjectState e = FutureUtil.getOrNull(f);
if (e != null && e.controlFor(user).isVisible()) {
- r.add(p.getNameKey());
+ res.add(e.getProject().getNameKey());
}
}
- return r;
+ return res;
}
});
}
@@ -84,40 +93,44 @@
final int max = 10;
final int n = limit <= 0 ? max : Math.min(limit, max);
- final LinkedHashMap<Account.Id, AccountInfo> r =
- new LinkedHashMap<Account.Id, AccountInfo>();
- for (final Account p : db.accounts().suggestByFullName(a, b, n)) {
- addSuggestion(r, p, new AccountInfo(p), active);
- }
- if (r.size() < n) {
- for (final Account p : db.accounts().suggestByPreferredEmail(a, b,
- n - r.size())) {
- addSuggestion(r, p, new AccountInfo(p), active);
+ LinkedHashMap<Account.Id, AccountInfo> res = Maps.newLinkedHashMap();
+ for (Account p : db.accounts().suggestByFullName(a, b, n)) {
+ if (active == null || active == p.isActive()) {
+ res.put(p.getId(), new AccountInfo(p));
}
}
- if (r.size() < n) {
- for (final AccountExternalId e : db.accountExternalIds()
- .suggestByEmailAddress(a, b, n - r.size())) {
- if (!r.containsKey(e.getAccountId())) {
- final Account p = accountCache.get(e.getAccountId()).getAccount();
- final AccountInfo info = new AccountInfo(p);
- info.setPreferredEmail(e.getEmailAddress());
- addSuggestion(r, p, info, active);
+ if (res.size() < n) {
+ for (Account p : db.accounts().suggestByPreferredEmail(a, b,
+ n - res.size())) {
+ if (active == null || active == p.isActive()) {
+ res.put(p.getId(), new AccountInfo(p));
}
}
}
- return new ArrayList<AccountInfo>(r.values());
+ if (res.size() < n) {
+ Map<String, Future<Account>> want = Maps.newHashMap();
+ for (AccountExternalId e : db.accountExternalIds()
+ .suggestByEmailAddress(a, b, n - res.size())) {
+ if (!res.containsKey(e.getAccountId())) {
+ want.put(e.getEmailAddress(), //
+ accountCache.getAccount(e.getAccountId()));
+ }
+ }
+
+ for (Map.Entry<String, Future<Account>> ent : want.entrySet()) {
+ Account p = FutureUtil.get(ent.getValue());
+ if (active == null || active == p.isActive()) {
+ AccountInfo info = new AccountInfo(p);
+ info.setPreferredEmail(ent.getKey());
+ res.put(p.getId(), info);
+ }
+ }
+ }
+ return new ArrayList<AccountInfo>(res.values());
}
});
}
- private void addSuggestion(Map map, Account account, AccountInfo info,
- Boolean active) {
- if (active == null || active == account.isActive()) {
- map.put(account.getId(), info);
- }
- }
-
public void suggestAccountGroup(final String query, final int limit,
final AsyncCallback<List<AccountGroupName>> callback) {
run(callback, new Action<List<AccountGroupName>>() {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
index 77662a1..f1aa8b9 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
@@ -14,6 +14,7 @@
package com.google.gerrit.httpd.rpc.account;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.common.data.AccountSecurity;
import com.google.gerrit.common.errors.ContactInformationStoreException;
import com.google.gerrit.common.errors.InvalidSshKeyException;
@@ -31,7 +32,7 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountByEmailCache;
+import com.google.gerrit.server.account.AccountAgreementsCache;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
@@ -45,6 +46,7 @@
import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.mail.RegisterNewEmailSender;
import com.google.gerrit.server.ssh.SshKeyCache;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtjsonrpc.server.ValidToken;
@@ -52,6 +54,7 @@
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.internal.Lists;
import org.eclipse.jgit.util.Base64;
import org.slf4j.Logger;
@@ -71,8 +74,8 @@
private final Provider<IdentifiedUser> user;
private final RegisterNewEmailSender.Factory registerNewEmailFactory;
private final SshKeyCache sshKeyCache;
- private final AccountByEmailCache byEmailCache;
private final AccountCache accountCache;
+ private final AccountAgreementsCache accountAgreementsCache;
private final AccountManager accountManager;
private final boolean useContactInfo;
@@ -88,7 +91,7 @@
final Provider<CurrentUser> currentUser, final ContactStore cs,
final AuthConfig ac, final Realm r, final Provider<IdentifiedUser> u,
final RegisterNewEmailSender.Factory esf, final SshKeyCache skc,
- final AccountByEmailCache abec, final AccountCache uac,
+ final AccountCache uac, final AccountAgreementsCache aac,
final AccountManager am,
final ClearPassword.Factory clearPasswordFactory,
final GeneratePassword.Factory generatePasswordFactory,
@@ -103,8 +106,8 @@
user = u;
registerNewEmailFactory = esf;
sshKeyCache = skc;
- byEmailCache = abec;
accountCache = uac;
+ accountAgreementsCache = aac;
accountManager = am;
useContactInfo = contactStore != null && contactStore.isEnabled();
@@ -143,7 +146,7 @@
throw new Failure(e);
}
db.accountSshKeys().insert(Collections.singleton(key));
- uncacheSshKeys();
+ FutureUtil.waitFor(sshKeyCache.evictAsync(user.get().getUserName()));
return key;
}
});
@@ -160,17 +163,12 @@
}
db.accountSshKeys().deleteKeys(ids);
- uncacheSshKeys();
-
+ FutureUtil.waitFor(sshKeyCache.evictAsync(user.get().getUserName()));
return VoidResult.INSTANCE;
}
});
}
- private void uncacheSshKeys() {
- sshKeyCache.evict(user.get().getUserName());
- }
-
@Override
public void changeUserName(final String newName,
final AsyncCallback<VoidResult> callback) {
@@ -211,6 +209,8 @@
final ContactInformation info, final AsyncCallback<Account> callback) {
run(callback, new Action<Account>() {
public Account run(ReviewDb db) throws OrmException, Failure {
+ List<ListenableFuture<Void>> evictions = Lists.newArrayList();
+
final Account me = db.accounts().get(user.get().getAccountId());
final String oldEmail = me.getPreferredEmail();
if (realm.allowsEdit(Account.FieldName.FULL_NAME)) {
@@ -232,10 +232,11 @@
}
db.accounts().update(Collections.singleton(me));
if (!eq(oldEmail, me.getPreferredEmail())) {
- byEmailCache.evict(oldEmail);
- byEmailCache.evict(me.getPreferredEmail());
+ evictions.add(accountCache.evictEmailAsync(oldEmail));
+ evictions.add(accountCache.evictEmailAsync(me.getPreferredEmail()));
}
- accountCache.evict(me.getId());
+ evictions.add(accountCache.evictAsync(me.getId()));
+ FutureUtil.waitFor(evictions);
return me;
}
});
@@ -264,6 +265,7 @@
a.review(AccountAgreement.Status.VERIFIED, null);
}
db.accountAgreements().insert(Collections.singleton(a));
+ FutureUtil.waitFor(accountAgreementsCache.evictAsync(a.getKey()));
return VoidResult.INSTANCE;
}
});
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
index 4fd8e28..0d16f81 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountServiceImpl.java
@@ -28,10 +28,13 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountDiffPreferencesCache;
+import com.google.gerrit.server.account.AccountProjectWatchCache;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmDuplicateKeyException;
@@ -51,6 +54,8 @@
private final AccountCache accountCache;
private final ProjectControl.Factory projectControlFactory;
private final AgreementInfoFactory.Factory agreementInfoFactory;
+ private final AccountProjectWatchCache accountProjectWatchCache;
+ private final AccountDiffPreferencesCache accountDiffPreferencesCache;
private final ChangeQueryBuilder.Factory queryBuilder;
@Inject
@@ -59,12 +64,16 @@
final AccountCache accountCache,
final ProjectControl.Factory projectControlFactory,
final AgreementInfoFactory.Factory agreementInfoFactory,
+ final AccountProjectWatchCache accountProjectWatchCache,
+ final AccountDiffPreferencesCache accountDiffPreferencesCache,
final ChangeQueryBuilder.Factory queryBuilder) {
super(schema, identifiedUser);
this.currentUser = identifiedUser;
this.accountCache = accountCache;
this.projectControlFactory = projectControlFactory;
this.agreementInfoFactory = agreementInfoFactory;
+ this.accountProjectWatchCache = accountProjectWatchCache;
+ this.accountDiffPreferencesCache = accountDiffPreferencesCache;
this.queryBuilder = queryBuilder;
}
@@ -92,7 +101,7 @@
}
a.setGeneralPreferences(pref);
db.accounts().update(Collections.singleton(a));
- accountCache.evict(a.getId());
+ FutureUtil.waitFor(accountCache.evictAsync(a.getId()));
return VoidResult.INSTANCE;
}
});
@@ -110,6 +119,7 @@
+ " the accountId of the signed in user " + getAccountId());
}
db.accountDiffPreferences().upsert(Collections.singleton(diffPref));
+ FutureUtil.waitFor(accountDiffPreferencesCache.evictAsync(accountId));
return VoidResult.INSTANCE;
}
});
@@ -122,13 +132,14 @@
final List<AccountProjectWatchInfo> r =
new ArrayList<AccountProjectWatchInfo>();
- for (final AccountProjectWatch w : db.accountProjectWatches()
- .byAccount(getAccountId()).toList()) {
+ for (AccountProjectWatch w : FutureUtil.get(accountProjectWatchCache
+ .byAccount(getAccountId()))) {
final ProjectControl ctl;
try {
ctl = projectControlFactory.validateFor(w.getProjectNameKey());
} catch (NoSuchProjectException e) {
db.accountProjectWatches().delete(Collections.singleton(w));
+ accountProjectWatchCache.evictAsync(w.getKey());
continue;
}
r.add(new AccountProjectWatchInfo(w, ctl.getProject()));
@@ -171,6 +182,7 @@
} catch (OrmDuplicateKeyException alreadyHave) {
watch = db.accountProjectWatches().get(watch.getKey());
}
+ FutureUtil.waitFor(accountProjectWatchCache.evictAsync(watch.getKey()));
return new AccountProjectWatchInfo(watch, ctl.getProject());
}
});
@@ -186,6 +198,7 @@
run(callback, new Action<VoidResult>() {
public VoidResult run(ReviewDb db) throws OrmException {
db.accountProjectWatches().update(Collections.singleton(watch));
+ FutureUtil.waitFor(accountProjectWatchCache.evictAsync(watch.getKey()));
return VoidResult.INSTANCE;
}
});
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java
index f638d48..bc45775 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java
@@ -14,21 +14,29 @@
package com.google.gerrit.httpd.rpc.account;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.common.data.AgreementInfo;
import com.google.gerrit.httpd.rpc.Handler;
+import com.google.gerrit.reviewdb.AbstractAgreement;
import com.google.gerrit.reviewdb.AccountAgreement;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountGroupAgreement;
import com.google.gerrit.reviewdb.ContributorAgreement;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountAgreementsCache;
+import com.google.gerrit.server.account.AccountGroupAgreementsCache;
+import com.google.gerrit.server.util.FutureUtil;
+import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
-import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Future;
class AgreementInfoFactory extends Handler<AgreementInfo> {
interface Factory {
@@ -37,52 +45,61 @@
private final ReviewDb db;
private final IdentifiedUser user;
+ private final AccountAgreementsCache accountAgreementsCache;
+ private final AccountGroupAgreementsCache accountGroupAgreementsCache;
private AgreementInfo info;
@Inject
- AgreementInfoFactory(final ReviewDb db, final IdentifiedUser user) {
+ AgreementInfoFactory(final ReviewDb db, final IdentifiedUser user,
+ final AccountAgreementsCache aac, final AccountGroupAgreementsCache agac) {
this.db = db;
this.user = user;
+ this.accountAgreementsCache = aac;
+ this.accountGroupAgreementsCache = agac;
}
@Override
public AgreementInfo call() throws Exception {
- final List<AccountAgreement> userAccepted =
- db.accountAgreements().byAccount(user.getAccountId()).toList();
+ Future<List<AccountAgreement>> wantUser =
+ accountAgreementsCache.byAccount(user.getAccountId());
- Collections.reverse(userAccepted);
-
- final List<AccountGroupAgreement> groupAccepted =
- new ArrayList<AccountGroupAgreement>();
- for (final AccountGroup.Id groupId : user.getEffectiveGroups()) {
- final List<AccountGroupAgreement> temp =
- db.accountGroupAgreements().byGroup(groupId).toList();
-
- Collections.reverse(temp);
-
- groupAccepted.addAll(temp);
+ List<ListenableFuture<List<AccountGroupAgreement>>> wantGroup =
+ Lists.newArrayList();
+ for (AccountGroup.Id groupId : user.getEffectiveGroups()) {
+ wantGroup.add(accountGroupAgreementsCache.byGroup(groupId));
}
- final Map<ContributorAgreement.Id, ContributorAgreement> agreements =
- new HashMap<ContributorAgreement.Id, ContributorAgreement>();
- for (final AccountAgreement a : userAccepted) {
- final ContributorAgreement.Id id = a.getAgreementId();
- if (!agreements.containsKey(id)) {
- agreements.put(id, db.contributorAgreements().get(id));
- }
- }
- for (final AccountGroupAgreement a : groupAccepted) {
- final ContributorAgreement.Id id = a.getAgreementId();
- if (!agreements.containsKey(id)) {
- agreements.put(id, db.contributorAgreements().get(id));
- }
- }
+ List<AccountAgreement> userAccepted = FutureUtil.getOrEmptyList(wantUser);
+ List<AccountGroupAgreement> groupAccepted =
+ FutureUtil.getOrEmptyList(FutureUtil.concat(wantGroup));
+
+ Collections.sort(userAccepted, AbstractAgreement.SORT);
+ Collections.sort(groupAccepted, AbstractAgreement.SORT);
info = new AgreementInfo();
info.setUserAccepted(userAccepted);
info.setGroupAccepted(groupAccepted);
- info.setAgreements(agreements);
+ info.setAgreements(agreements(userAccepted, groupAccepted));
return info;
}
+
+ private Map<ContributorAgreement.Id, ContributorAgreement> agreements(
+ List<AccountAgreement> userAccepted,
+ List<AccountGroupAgreement> groupAccepted) throws OrmException {
+ Map<ContributorAgreement.Id, ContributorAgreement> all = Maps.newHashMap();
+ for (AccountAgreement a : userAccepted) {
+ ContributorAgreement.Id id = a.getAgreementId();
+ if (!all.containsKey(id)) {
+ all.put(id, db.contributorAgreements().get(id));
+ }
+ }
+ for (AccountGroupAgreement a : groupAccepted) {
+ ContributorAgreement.Id id = a.getAgreementId();
+ if (!all.containsKey(id)) {
+ all.put(id, db.contributorAgreements().get(id));
+ }
+ }
+ return all;
+ }
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/CreateGroup.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/CreateGroup.java
index 74e2966..6c10f9c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/CreateGroup.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/CreateGroup.java
@@ -24,6 +24,7 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
@@ -83,7 +84,7 @@
Collections.singleton(new AccountGroupMemberAudit(member, me)));
db.accountGroupMembers().insert(Collections.singleton(member));
- accountCache.evict(me);
+ FutureUtil.waitFor(accountCache.evictAsync(me));
return id;
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/DeleteExternalIds.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/DeleteExternalIds.java
index 4edd571..3c2cb15 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/DeleteExternalIds.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/DeleteExternalIds.java
@@ -14,15 +14,17 @@
package com.google.gerrit.httpd.rpc.account;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountByEmailCache;
import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import com.google.inject.internal.Lists;
import java.util.ArrayList;
import java.util.HashMap;
@@ -39,7 +41,6 @@
private final ReviewDb db;
private final IdentifiedUser user;
private final ExternalIdDetailFactory detailFactory;
- private final AccountByEmailCache byEmailCache;
private final AccountCache accountCache;
private final Set<AccountExternalId.Key> keys;
@@ -47,13 +48,12 @@
@Inject
DeleteExternalIds(final ReviewDb db, final IdentifiedUser user,
final ExternalIdDetailFactory detailFactory,
- final AccountByEmailCache byEmailCache, final AccountCache accountCache,
+ final AccountCache accountCache,
@Assisted final Set<AccountExternalId.Key> keys) {
this.db = db;
this.user = user;
this.detailFactory = detailFactory;
- this.byEmailCache = byEmailCache;
this.accountCache = accountCache;
this.keys = keys;
@@ -71,19 +71,20 @@
}
}
+ List<ListenableFuture<Void>> evictions = Lists.newArrayList();
if (!toDelete.isEmpty()) {
db.accountExternalIds().delete(toDelete);
- accountCache.evict(user.getAccountId());
+ evictions.add(accountCache.evictAsync(user.getAccountId()));
for (AccountExternalId e : toDelete) {
- byEmailCache.evict(e.getEmailAddress());
+ evictions.add(accountCache.evictEmailAsync(e.getEmailAddress()));
+ evictions.add(accountCache.evictAsync(e.getKey()));
}
}
-
+ FutureUtil.waitFor(evictions);
return toKeySet(toDelete);
}
- private Map<AccountExternalId.Key, AccountExternalId> have()
- throws OrmException {
+ private Map<AccountExternalId.Key, AccountExternalId> have() {
Map<AccountExternalId.Key, AccountExternalId> r;
r = new HashMap<AccountExternalId.Key, AccountExternalId>();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/ExternalIdDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/ExternalIdDetailFactory.java
index de0dec9..42ee569 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/ExternalIdDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/ExternalIdDetailFactory.java
@@ -16,42 +16,43 @@
import static com.google.gerrit.reviewdb.AccountExternalId.SCHEME_USERNAME;
+import com.google.common.collect.Lists;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountExternalId;
-import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AuthConfig;
-import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.protobuf.CodecFactory;
+import com.google.gwtorm.protobuf.ProtobufCodec;
import com.google.inject.Inject;
import java.util.Collections;
import java.util.List;
class ExternalIdDetailFactory extends Handler<List<AccountExternalId>> {
+ private static final ProtobufCodec<AccountExternalId> codec =
+ CodecFactory.encoder(AccountExternalId.class);
+
interface Factory {
ExternalIdDetailFactory create();
}
- private final ReviewDb db;
private final IdentifiedUser user;
private final AuthConfig authConfig;
private final WebSession session;
@Inject
- ExternalIdDetailFactory(final ReviewDb db, final IdentifiedUser user,
+ ExternalIdDetailFactory(final IdentifiedUser user,
final AuthConfig authConfig, final WebSession session) {
- this.db = db;
this.user = user;
this.authConfig = authConfig;
this.session = session;
}
@Override
- public List<AccountExternalId> call() throws OrmException {
- final AccountExternalId.Key last = session.getLastLoginExternalId();
- final List<AccountExternalId> ids =
- db.accountExternalIds().byAccount(user.getAccountId()).toList();
+ public List<AccountExternalId> call() {
+ AccountExternalId.Key last = session.getLastLoginExternalId();
+ List<AccountExternalId> ids = load();
for (final AccountExternalId e : ids) {
e.setTrusted(authConfig.isIdentityTrustable(Collections.singleton(e)));
@@ -66,6 +67,19 @@
e.setCanDelete(last != null && !last.equals(e.getKey()));
}
}
+
return ids;
}
+
+ private List<AccountExternalId> load() {
+ List<AccountExternalId> res = Lists.newArrayList();
+ for (AccountExternalId id : user.getAccountState().getExternalIds()) {
+ res.add(clone(id));
+ }
+ return res;
+ }
+
+ private static AccountExternalId clone(AccountExternalId id) {
+ return codec.decode(codec.encodeToByteArray(id));
+ }
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
index 870d77c..eb12537 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupAdminServiceImpl.java
@@ -14,6 +14,7 @@
package com.google.gerrit.httpd.rpc.account;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.common.data.GroupAdminService;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.common.errors.InactiveAccountException;
@@ -33,11 +34,13 @@
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.NoSuchGroupException;
import com.google.gerrit.server.account.Realm;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import com.google.inject.internal.Lists;
import java.util.ArrayList;
import java.util.Collections;
@@ -135,7 +138,7 @@
assertAmGroupOwner(db, group);
group.setDescription(description);
db.accountGroups().update(Collections.singleton(group));
- groupCache.evict(group);
+ FutureUtil.waitFor(groupCache.evictAsync(group));
return VoidResult.INSTANCE;
}
});
@@ -149,14 +152,15 @@
assertAmGroupOwner(db, group);
final AccountGroup owner =
- groupCache.get(new AccountGroup.NameKey(newOwnerName));
+ FutureUtil.get(groupCache
+ .get(new AccountGroup.NameKey(newOwnerName)));
if (owner == null) {
throw new Failure(new NoSuchEntityException());
}
group.setOwnerGroupId(owner.getId());
db.accountGroups().update(Collections.singleton(group));
- groupCache.evict(group);
+ FutureUtil.waitFor(groupCache.evictAsync(group));
return VoidResult.INSTANCE;
}
});
@@ -175,7 +179,7 @@
assertAmGroupOwner(db, group);
group.setType(newType);
db.accountGroups().update(Collections.singleton(group));
- groupCache.evict(group);
+ FutureUtil.waitFor(groupCache.evictAsync(group));
return VoidResult.INSTANCE;
}
});
@@ -190,7 +194,7 @@
assertAmGroupOwner(db, group);
group.setExternalNameKey(bindTo);
db.accountGroups().update(Collections.singleton(group));
- groupCache.evict(group);
+ FutureUtil.waitFor(groupCache.evictAsync(group));
return VoidResult.INSTANCE;
}
});
@@ -238,7 +242,7 @@
Collections.singleton(new AccountGroupMemberAudit(m,
getAccountId())));
db.accountGroupMembers().insert(Collections.singleton(m));
- accountCache.evict(m.getAccountId());
+ FutureUtil.waitFor(accountCache.evictAsync(m.getAccountId()));
}
return groupDetailFactory.create(groupId).call();
@@ -263,6 +267,7 @@
}
}
+ List<ListenableFuture<Void>> evictions = Lists.newArrayList();
final Account.Id me = getAccountId();
for (final AccountGroupMember.Key k : keys) {
final AccountGroupMember m = db.accountGroupMembers().get(k);
@@ -292,9 +297,10 @@
}
db.accountGroupMembers().delete(Collections.singleton(m));
- accountCache.evict(m.getAccountId());
+ evictions.add(accountCache.evictAsync(m.getAccountId()));
}
}
+ FutureUtil.waitFor(evictions);
return VoidResult.INSTANCE;
}
});
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupDetailFactory.java
index 1b05660..8adae93 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/GroupDetailFactory.java
@@ -24,6 +24,7 @@
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.NoSuchGroupException;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -65,12 +66,13 @@
final AccountGroup group = control.getAccountGroup();
final GroupDetail detail = new GroupDetail();
detail.setGroup(group);
- detail.setOwnerGroup(groupCache.get(group.getOwnerGroupId()));
switch (group.getType()) {
case INTERNAL:
detail.setMembers(loadMembers());
break;
}
+ detail.setOwnerGroup(FutureUtil.get( //
+ groupCache.get(group.getOwnerGroupId())));
detail.setAccounts(aic.create());
return detail;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/MyGroupsFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/MyGroupsFactory.java
index b3e993e..af251f6 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/MyGroupsFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/MyGroupsFactory.java
@@ -14,17 +14,18 @@
package com.google.gerrit.httpd.rpc.account;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.inject.Inject;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import java.util.Set;
class MyGroupsFactory extends Handler<List<AccountGroup>> {
interface Factory {
@@ -42,18 +43,18 @@
@Override
public List<AccountGroup> call() throws Exception {
- final Set<AccountGroup.Id> effective = user.getEffectiveGroups();
- final int cnt = effective.size();
- final List<AccountGroup> groupList = new ArrayList<AccountGroup>(cnt);
- for (final AccountGroup.Id groupId : effective) {
- groupList.add(groupCache.get(groupId));
+ List<ListenableFuture<AccountGroup>> want = Lists.newArrayList();
+ for (AccountGroup.Id id : user.getEffectiveGroups()) {
+ want.add(groupCache.get(id));
}
- Collections.sort(groupList, new Comparator<AccountGroup>() {
+
+ List<AccountGroup> all = FutureUtil.get(FutureUtil.concatSingletons(want));
+ Collections.sort(all, new Comparator<AccountGroup>() {
@Override
public int compare(AccountGroup a, AccountGroup b) {
return a.getName().compareTo(b.getName());
}
});
- return groupList;
+ return all;
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java
index 63b5bce..b512f0f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/RenameGroup.java
@@ -14,6 +14,7 @@
package com.google.gerrit.httpd.rpc.account;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountGroup;
@@ -22,13 +23,16 @@
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.NoSuchGroupException;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import com.google.inject.internal.Lists;
import java.util.Collections;
+import java.util.List;
class RenameGroup extends Handler<VoidResult> {
interface Factory {
@@ -91,9 +95,10 @@
db.accountGroupNames().delete(Collections.singleton(priorName));
}
- groupCache.evict(group);
- groupCache.evictAfterRename(old);
-
+ List<ListenableFuture<Void>> evictions = Lists.newArrayList();
+ evictions.add(groupCache.evictAsync(group));
+ evictions.add(groupCache.evictAfterRenameAsync(old));
+ FutureUtil.waitFor(evictions);
return VoidResult.INSTANCE;
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
index 0fcdf40..e8fa475 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetDetailFactory.java
@@ -31,6 +31,7 @@
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -80,7 +81,7 @@
}
}
- final PatchList list = patchListCache.get(control.getChange(), patchSet);
+ PatchList list = FutureUtil.get(patchListCache.get(control.getChange(), patchSet));
if (list == null) {
throw new NoSuchEntityException();
}
@@ -91,7 +92,7 @@
byKey.put(p.getKey(), p);
}
- for (final PatchLineComment c : db.patchComments().published(psId)) {
+ for (final PatchLineComment c : db.patchComments().publishedByPatchSet(psId)) {
final Patch p = byKey.get(c.getKey().getParentKey());
if (p != null) {
p.setCommentCount(p.getCommentCount() + 1);
@@ -111,7 +112,7 @@
// quickly locate where they have pending drafts, and review them.
//
final Account.Id me = ((IdentifiedUser) user).getAccountId();
- for (final PatchLineComment c : db.patchComments().draft(psId, me)) {
+ for (final PatchLineComment c : db.patchComments().draftByPatchSet(psId, me)) {
final Patch p = byKey.get(c.getKey().getParentKey());
if (p != null) {
p.setDraftCount(p.getDraftCount() + 1);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
index 779475e..321ed5f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
@@ -36,7 +36,6 @@
import com.google.gerrit.server.project.CanSubmitResult;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefControl;
import com.google.gwtorm.client.OrmException;
@@ -54,7 +53,6 @@
PatchSetPublishDetailFactory create(PatchSet.Id patchSetId);
}
- private final ProjectCache projectCache;
private final PatchSetInfoFactory infoFactory;
private final ApprovalTypes approvalTypes;
private final ReviewDb db;
@@ -70,15 +68,14 @@
private List<PatchLineComment> drafts;
private Map<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>> allowed;
private Map<ApprovalCategory.Id, PatchSetApproval> given;
+ private ChangeControl control;
@Inject
PatchSetPublishDetailFactory(final PatchSetInfoFactory infoFactory,
- final ProjectCache projectCache, final ApprovalTypes approvalTypes,
- final ReviewDb db,
+ final ApprovalTypes approvalTypes, final ReviewDb db,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
final ChangeControl.Factory changeControlFactory,
final IdentifiedUser user, @Assisted final PatchSet.Id patchSetId) {
- this.projectCache = projectCache;
this.infoFactory = infoFactory;
this.approvalTypes = approvalTypes;
this.db = db;
@@ -93,10 +90,10 @@
public PatchSetPublishDetail call() throws OrmException,
PatchSetInfoNotAvailableException, NoSuchChangeException {
final Change.Id changeId = patchSetId.getParentKey();
- final ChangeControl control = changeControlFactory.validateFor(changeId);
+ control = changeControlFactory.validateFor(changeId);
change = control.getChange();
patchSetInfo = infoFactory.get(patchSetId);
- drafts = db.patchComments().draft(patchSetId, user.getAccountId()).toList();
+ drafts = db.patchComments().draftByPatchSet(patchSetId, user.getAccountId()).toList();
allowed = new HashMap<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>>();
given = new HashMap<ApprovalCategory.Id, PatchSetApproval>();
@@ -128,7 +125,7 @@
private void computeAllowed() {
final Set<AccountGroup.Id> am = user.getEffectiveGroups();
- final ProjectState pe = projectCache.get(change.getProject());
+ final ProjectState pe = control.getProjectControl().getProjectState();
for (ApprovalCategory.Id category : approvalTypes.getApprovalCategories()) {
RefControl rc = pe.controlFor(user).controlForRef(change.getDest());
List<RefRight> categoryRights = rc.getApplicableRights(category);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
index 9218edd..f631bd4 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
@@ -17,7 +17,9 @@
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.PatchScript.DisplayMethod;
+import com.google.gerrit.prettify.common.BaseEdit;
import com.google.gerrit.prettify.common.EditList;
+import com.google.gerrit.prettify.common.LineEdit;
import com.google.gerrit.prettify.common.SparseFileContent;
import com.google.gerrit.reviewdb.AccountDiffPreference;
import com.google.gerrit.reviewdb.Change;
@@ -33,7 +35,6 @@
import eu.medsea.mimeutil.MimeType;
import eu.medsea.mimeutil.MimeUtil2;
-import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -55,9 +56,9 @@
static final int MAX_CONTEXT = 5000000;
static final int BIG_FILE = 9000;
- private static final Comparator<Edit> EDIT_SORT = new Comparator<Edit>() {
+ private static final Comparator<BaseEdit> EDIT_SORT = new Comparator<BaseEdit>() {
@Override
- public int compare(final Edit o1, final Edit o2) {
+ public int compare(final BaseEdit o1, final BaseEdit o2) {
return o1.getBeginA() - o2.getBeginA();
}
};
@@ -71,7 +72,7 @@
private final Side a;
private final Side b;
- private List<Edit> edits;
+ private List<LineEdit> edits;
private final FileTypeRegistry registry;
private int context;
@@ -115,7 +116,7 @@
//
return new PatchScript(change.getKey(), content.getChangeType(), content
.getOldName(), content.getNewName(), content.getHeaderLines(),
- diffPrefs, a.dst, b.dst, Collections.<Edit> emptyList(),
+ diffPrefs, a.dst, b.dst, Collections.<LineEdit> emptyList(),
a.displayMethod, b.displayMethod, comments, history, false, false);
}
@@ -125,7 +126,7 @@
a.resolve(null, aId);
b.resolve(a, bId);
- edits = new ArrayList<Edit>(content.getEdits());
+ edits = new ArrayList<LineEdit>(content.getEdits());
ensureCommentsVisible(comments);
boolean hugeFile = false;
@@ -140,8 +141,8 @@
for (int i = 0; i < a.size(); i++) {
a.addLine(i);
}
- edits = new ArrayList<Edit>(1);
- edits.add(new Edit(a.size(), a.size()));
+ edits = new ArrayList<LineEdit>(1);
+ edits.add(new LineEdit(a.size(), a.size()));
} else {
if (BIG_FILE < Math.max(a.size(), b.size())) {
@@ -210,7 +211,7 @@
// correct hunks from this, but because the Edit is empty they will not
// style it specially.
//
- final List<Edit> empty = new ArrayList<Edit>();
+ final List<LineEdit> empty = new ArrayList<LineEdit>();
int lastLine;
lastLine = -1;
@@ -219,7 +220,7 @@
if (lastLine != a) {
final int b = mapA2B(a - 1);
if (0 <= b) {
- safeAdd(empty, new Edit(a - 1, b));
+ safeAdd(empty, new LineEdit(a - 1, b));
}
lastLine = a;
}
@@ -231,7 +232,7 @@
if (lastLine != b) {
final int a = mapB2A(b - 1);
if (0 <= a) {
- safeAdd(empty, new Edit(a, b - 1));
+ safeAdd(empty, new LineEdit(a, b - 1));
}
lastLine = b;
}
@@ -244,10 +245,10 @@
Collections.sort(edits, EDIT_SORT);
}
- private void safeAdd(final List<Edit> empty, final Edit toAdd) {
+ private void safeAdd(final List<LineEdit> empty, final LineEdit toAdd) {
final int a = toAdd.getBeginA();
final int b = toAdd.getBeginB();
- for (final Edit e : edits) {
+ for (final BaseEdit e : edits) {
if (e.getBeginA() <= a && a <= e.getEndA()) {
return;
}
@@ -266,7 +267,7 @@
}
for (int i = 0; i < edits.size(); i++) {
- final Edit e = edits.get(i);
+ final BaseEdit e = edits.get(i);
if (a < e.getBeginA()) {
if (i == 0) {
// Special case of context at start of file.
@@ -280,7 +281,7 @@
}
}
- final Edit last = edits.get(edits.size() - 1);
+ final BaseEdit last = edits.get(edits.size() - 1);
return last.getBeginB() + (a - last.getEndA());
}
@@ -292,7 +293,7 @@
}
for (int i = 0; i < edits.size(); i++) {
- final Edit e = edits.get(i);
+ final BaseEdit e = edits.get(i);
if (b < e.getBeginB()) {
if (i == 0) {
// Special case of context at start of file.
@@ -306,12 +307,13 @@
}
}
- final Edit last = edits.get(edits.size() - 1);
+ final BaseEdit last = edits.get(edits.size() - 1);
return last.getBeginA() + (b - last.getEndB());
}
private void packContent(boolean ignoredWhitespace) {
- EditList list = new EditList(edits, context, a.size(), b.size());
+ EditList list =
+ new EditList(edits, context, a.size(), b.size());
for (final EditList.Hunk hunk : list.getHunks()) {
while (hunk.next()) {
if (hunk.isContextLine()) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
index ee82418..597bf01 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
@@ -37,6 +37,7 @@
import com.google.gerrit.server.patch.PatchListKey;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -168,7 +169,7 @@
}
private PatchList listFor(final PatchListKey key) {
- return patchListCache.get(key);
+ return FutureUtil.get(patchListCache.get(key));
}
private PatchScriptBuilder newBuilder(final PatchList list, Repository git) {
@@ -288,7 +289,7 @@
private void loadPublished(final Map<Patch.Key, Patch> byKey,
final AccountInfoCacheFactory aic, final String file) throws OrmException {
- for (PatchLineComment c : db.patchComments().published(changeId, file)) {
+ for (PatchLineComment c : db.patchComments().publishedByChangeFile(changeId, file)) {
if (comments.include(c)) {
aic.want(c.getAuthor());
}
@@ -304,7 +305,7 @@
private void loadDrafts(final Map<Patch.Key, Patch> byKey,
final AccountInfoCacheFactory aic, final Account.Id me, final String file)
throws OrmException {
- for (PatchLineComment c : db.patchComments().draft(changeId, file, me)) {
+ for (PatchLineComment c : db.patchComments().draftByChangeFile(changeId, file, me)) {
if (comments.include(c)) {
aic.want(me);
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java
index 3c2c93f..f4e551c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java
@@ -31,6 +31,7 @@
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -183,7 +184,7 @@
throw new NoSuchRefException(refPattern);
}
- final AccountGroup group = groupCache.get(groupName);
+ final AccountGroup group = FutureUtil.get(groupCache.get(groupName));
if (group == null) {
throw new NoSuchGroupException(groupName);
}
@@ -201,7 +202,7 @@
rr.setMaxValue(max);
db.refRights().update(Collections.singleton(rr));
}
- projectCache.evictAll();
+ FutureUtil.waitFor(projectCache.evictAllAsync());
return projectDetailFactory.create(projectName).call();
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectSettings.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectSettings.java
index 8831e90..c46d28e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectSettings.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectSettings.java
@@ -22,6 +22,7 @@
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -70,7 +71,7 @@
proj.copySettingsFrom(update);
db.projects().update(Collections.singleton(proj));
- projectCache.evict(proj);
+ FutureUtil.waitFor(projectCache.evictAsync(proj));
if (!projectControl.getProjectState().isSpecialWildProject()) {
repoManager.setProjectDescription(projectName.get(), update.getDescription());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteRefRights.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteRefRights.java
index 92154c4..19a1f80 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteRefRights.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteRefRights.java
@@ -23,7 +23,7 @@
import com.google.gerrit.server.project.NoSuchRefException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -82,7 +82,7 @@
db.refRights().delete(Collections.singleton(m));
}
}
- projectCache.evictAll();
+ FutureUtil.waitFor(projectCache.evictAllAsync());
return projectDetailFactory.create(projectName).call();
}
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java
index 3ff3892..dfa5a87 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java
@@ -14,6 +14,8 @@
package com.google.gerrit.httpd.rpc.project;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.InheritedRefRight;
@@ -26,16 +28,15 @@
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
+import java.util.concurrent.Future;
class ProjectDetailFactory extends Handler<ProjectDetail> {
interface Factory {
@@ -47,7 +48,7 @@
private final ProjectControl.Factory projectControlFactory;
private final Project.NameKey projectName;
- private Map<AccountGroup.Id, AccountGroup> groups;
+ private Map<AccountGroup.Id, Future<AccountGroup>> groups;
@Inject
ProjectDetailFactory(final ApprovalTypes approvalTypes,
@@ -71,26 +72,25 @@
final ProjectDetail detail = new ProjectDetail();
detail.setProject(projectState.getProject());
- groups = new HashMap<AccountGroup.Id, AccountGroup>();
- final List<InheritedRefRight> refRights = new ArrayList<InheritedRefRight>();
+ groups = Maps.newHashMap();
+ final List<InheritedRefRight> refRights = Lists.newArrayList();
for (final RefRight r : projectState.getInheritedRights()) {
- InheritedRefRight refRight = new InheritedRefRight(
- r, true, pc.controlForRef(r.getRefPattern()).isOwner());
+ InheritedRefRight refRight =
+ new InheritedRefRight(r, true, pc.controlForRef(r.getRefPattern())
+ .isOwner());
if (!refRights.contains(refRight)) {
refRights.add(refRight);
- wantGroup(r.getAccountGroupId());
+ want(r.getAccountGroupId());
}
}
for (final RefRight r : projectState.getLocalRights()) {
- refRights.add(new InheritedRefRight(
- r, false, pc.controlForRef(r.getRefPattern()).isOwner()));
- wantGroup(r.getAccountGroupId());
+ refRights.add(new InheritedRefRight(r, false, pc.controlForRef(
+ r.getRefPattern()).isOwner()));
+ want(r.getAccountGroupId());
}
- loadGroups();
-
Collections.sort(refRights, new Comparator<InheritedRefRight>() {
@Override
public int compare(final InheritedRefRight a, final InheritedRefRight b) {
@@ -115,8 +115,8 @@
return type.getCategory().getName();
}
- private String groupOf(final RefRight r) {
- return groups.get(r.getAccountGroupId()).getName();
+ private String groupOf(RefRight r) {
+ return FutureUtil.get(want(r.getAccountGroupId())).getName();
}
});
@@ -124,7 +124,7 @@
final boolean userIsOwnerAnyRef = pc.isOwnerAnyRef();
detail.setRights(refRights);
- detail.setGroups(groups);
+ detail.setGroups(FutureUtil.getMap(groups));
detail.setCanModifyAccess(userIsOwnerAnyRef);
detail.setCanModifyAgreements(userIsOwner);
detail.setCanModifyDescription(userIsOwner);
@@ -132,15 +132,12 @@
return detail;
}
- private void wantGroup(final AccountGroup.Id id) {
- groups.put(id, null);
- }
-
- private void loadGroups() {
- final Set<AccountGroup.Id> toGet = groups.keySet();
- groups = new HashMap<AccountGroup.Id, AccountGroup>();
- for (AccountGroup.Id groupId : toGet) {
- groups.put(groupId, groupCache.get(groupId));
+ private Future<AccountGroup> want(AccountGroup.Id id) {
+ Future<AccountGroup> f = groups.get(id);
+ if (f == null) {
+ f = groupCache.get(id);
+ groups.put(id, f);
}
+ return f;
}
}
diff --git a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/ReplaceEdit.java b/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/ReplaceEdit.java
deleted file mode 100644
index 46681c6..0000000
--- a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/ReplaceEdit.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2010 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package org.eclipse.jgit.diff;
-
-import java.util.List;
-
-public class ReplaceEdit extends Edit {
- private List<Edit> internalEdit;
-
- public ReplaceEdit(int as, int ae, int bs, int be, List<Edit> internal) {
- super(as, ae, bs, be);
- internalEdit = internal;
- }
-
- public ReplaceEdit(Edit orig, List<Edit> internal) {
- super(orig.getBeginA(), orig.getEndA(), orig.getBeginB(), orig.getEndB());
- internalEdit = internal;
- }
-
- public List<Edit> getInternalEdits() {
- return internalEdit;
- }
-}
diff --git a/gerrit-pgm/pom.xml b/gerrit-pgm/pom.xml
index f658f52..d3eee42 100644
--- a/gerrit-pgm/pom.xml
+++ b/gerrit-pgm/pom.xml
@@ -49,6 +49,11 @@
</dependency>
<dependency>
+ <groupId>com.google.protobuf</groupId>
+ <artifactId>protobuf-java</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-main</artifactId>
<version>${project.version}</version>
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Backup.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Backup.java
new file mode 100644
index 0000000..dc8a3ef
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Backup.java
@@ -0,0 +1,141 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm;
+
+import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
+
+import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.pgm.util.SiteProgram;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.schema.backup.BackupAccess;
+import com.google.gerrit.server.schema.backup.BackupDatabase;
+import com.google.gerrit.server.schema.backup.Counters;
+import com.google.gwtorm.client.Access;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.SchemaFactory;
+import com.google.gwtorm.protobuf.ProtobufCodec;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.TextProgressMonitor;
+import org.eclipse.jgit.storage.file.LockFile;
+import org.eclipse.jgit.util.FS;
+import org.kohsuke.args4j.Option;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.GZIPOutputStream;
+
+/** Backup the database as a compressed series of protobuf objects. */
+public class Backup extends SiteProgram {
+ private final LifecycleManager manager = new LifecycleManager();
+ private Injector dbInjector;
+
+ @Option(name = "--quiet", aliases = {"-q"}, usage = "Quiet (suppress progress)")
+ private boolean quiet;
+
+ @Option(name = "--output", aliases = {"-o"}, required = true, metaVar = "FILE", usage = "File to write the backup into")
+ private File output;
+
+ @Inject
+ private SchemaFactory<ReviewDb> srcdb;
+
+ @Override
+ public int run() throws Exception {
+ mustHaveValidSite();
+
+ dbInjector = createDbInjector(SINGLE_USER);
+ manager.add(dbInjector);
+ manager.start();
+ dbInjector.injectMembers(this);
+
+ BackupDatabase<ReviewDb> bck = new BackupDatabase<ReviewDb>(ReviewDb.class);
+ ReviewDb dst = bck.open();
+
+ ReviewDb src = srcdb.open();
+ try {
+ final LockFile lf = new LockFile(output.getAbsoluteFile(), FS.DETECTED);
+ if (!lf.lock()) {
+ throw die("Cannot lock " + output);
+ }
+ try {
+ BufferedOutputStream out =
+ new BufferedOutputStream(new GZIPOutputStream(lf.getOutputStream(),
+ 8192));
+ try {
+ backup(src, dst, out);
+ } finally {
+ out.close();
+ }
+ if (!lf.commit()) {
+ throw die("Cannot commit " + output);
+ }
+ if (!quiet) {
+ System.err.println("Backup completed to " + output);
+ }
+ } finally {
+ lf.unlock();
+ }
+ } finally {
+ src.close();
+ }
+ return 0;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void backup(ReviewDb src, ReviewDb dst, BufferedOutputStream out)
+ throws OrmException, IOException {
+ Map<Integer, BackupAccess<?, ?>> relations = index(dst);
+
+ ProgressMonitor pm =
+ quiet ? NullProgressMonitor.INSTANCE : new TextProgressMonitor();
+
+ Counters cnts = new Counters();
+ cnts.accountGroupId = src.nextAccountGroupId();
+ cnts.accountId = src.nextAccountId();
+ cnts.changeId = src.nextChangeId();
+ cnts.changeMessageId = src.nextChangeMessageId();
+ cnts.contributorAgreementId = src.nextContributorAgreementId();
+ Counters.CODEC.encodeWithSize(cnts, out);
+
+ for (Access<?, ?> s : src.allRelations()) {
+ BackupAccess<?, ?> d = relations.get(s.getRelationID());
+ ProtobufCodec pc = d.getObjectCodec();
+
+ pm.beginTask("Backup " + s.getRelationName(), ProgressMonitor.UNKNOWN);
+ for (Object obj : s.iterateAllEntities()) {
+ pc.encodeWithSize(obj, out);
+ pm.update(1);
+ }
+ pm.endTask();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map<Integer, BackupAccess<?, ?>> index(ReviewDb dst) {
+ Map<Integer, BackupAccess<?, ?>> relations =
+ new HashMap<Integer, BackupAccess<?, ?>>();
+
+ for (Access<?, ?> a : dst.allRelations()) {
+ relations.put(a.getRelationID(), (BackupAccess) a);
+ }
+ return relations;
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 3e10336..d9885f2 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -16,8 +16,11 @@
import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
+import com.google.gerrit.ehcache.EhcachePoolImpl;
import com.google.gerrit.httpd.HttpCanonicalWebUrlProvider;
+import com.google.gerrit.httpd.SessionCacheCleaner;
import com.google.gerrit.httpd.WebModule;
+import com.google.gerrit.httpd.WebSshGlueModule;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.pgm.http.jetty.JettyEnv;
import com.google.gerrit.pgm.http.jetty.JettyModule;
@@ -31,6 +34,9 @@
import com.google.gerrit.server.config.CanonicalWebUrlProvider;
import com.google.gerrit.server.config.GerritGlobalModule;
import com.google.gerrit.server.config.MasterNodeStartup;
+import com.google.gerrit.server.git.LocalDiskRepositoryManager;
+import com.google.gerrit.server.mail.SmtpEmailSender;
+import com.google.gerrit.server.ssh.NoSshModule;
import com.google.gerrit.sshd.SshModule;
import com.google.gerrit.sshd.commands.MasterCommandModule;
import com.google.gerrit.sshd.commands.SlaveCommandModule;
@@ -113,10 +119,6 @@
if (slave && httpd) {
throw die("Cannot combine --slave and --enable-httpd");
}
- if (httpd && !sshd) {
- // TODO Support HTTP without SSH.
- throw die("--enable-httpd currently requires --enable-sshd");
- }
if (consoleLog) {
} else {
@@ -187,6 +189,9 @@
final List<Module> modules = new ArrayList<Module>();
modules.add(new LogFileCompressor.Module());
modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
+ modules.add(new SmtpEmailSender.Module());
+ modules.add(new LocalDiskRepositoryManager.Module());
+ modules.add(new EhcachePoolImpl.Module());
if (httpd) {
modules.add(new CanonicalWebUrlModule() {
@Override
@@ -215,11 +220,15 @@
private Injector createSshInjector() {
final List<Module> modules = new ArrayList<Module>();
- modules.add(new SshModule());
- if (slave) {
- modules.add(new SlaveCommandModule());
+ if (sshd) {
+ modules.add(new SshModule());
+ if (slave) {
+ modules.add(new SlaveCommandModule());
+ } else {
+ modules.add(new MasterCommandModule());
+ }
} else {
- modules.add(new MasterCommandModule());
+ modules.add(new NoSshModule());
}
return sysInjector.createChildInjector(modules);
}
@@ -237,8 +246,14 @@
private Injector createWebInjector() {
final List<Module> modules = new ArrayList<Module>();
- modules.add(sshInjector.getInstance(ProjectQoSFilter.Module.class));
+ if (sshd) {
+ modules.add(sshInjector.getInstance(ProjectQoSFilter.Module.class));
+ }
modules.add(sshInjector.getInstance(WebModule.class));
+ modules.add(sshInjector.getInstance(WebSshGlueModule.class));
+ if (!slave) {
+ modules.add(new SessionCacheCleaner.Module());
+ }
return sysInjector.createChildInjector(modules);
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtoGen.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtoGen.java
new file mode 100644
index 0000000..0629cff
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/ProtoGen.java
@@ -0,0 +1,66 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm;
+
+import com.google.gerrit.pgm.util.AbstractProgram;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gwtorm.schema.java.JavaSchemaModel;
+
+import org.eclipse.jgit.storage.file.LockFile;
+import org.eclipse.jgit.util.FS;
+import org.kohsuke.args4j.Option;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+public class ProtoGen extends AbstractProgram {
+ @Option(name = "--output", aliases = {"-o"}, required = true, metaVar = "FILE", usage = "File to write .proto into")
+ private File file;
+
+ @Override
+ public int run() throws Exception {
+ LockFile lf = new LockFile(file.getAbsoluteFile(), FS.DETECTED);
+ if (!lf.lock()) {
+ throw die("Cannot lock " + file);
+ }
+ try {
+ JavaSchemaModel jsm = new JavaSchemaModel(ReviewDb.class);
+ PrintWriter out =
+ new PrintWriter(new BufferedWriter(new OutputStreamWriter(lf
+ .getOutputStream(), "UTF-8")));
+ try {
+ out.println("// Gerrit Code Review (version "
+ + com.google.gerrit.common.Version.getVersion() + ")");
+ out.println();
+
+ out.println("package gerritcodereview;\n");
+ out.println();
+
+ jsm.generateProto(out);
+ out.flush();
+ } finally {
+ out.close();
+ }
+ if (!lf.commit()) {
+ throw die("Could not write to " + file);
+ }
+ } finally {
+ lf.unlock();
+ }
+ return 0;
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Restore.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Restore.java
new file mode 100644
index 0000000..01611fc
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Restore.java
@@ -0,0 +1,76 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm;
+
+import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
+
+import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.pgm.util.SiteProgram;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.schema.backup.RestoreBackup;
+import com.google.gwtorm.client.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+import org.kohsuke.args4j.Option;
+
+import java.io.File;
+import java.io.FileInputStream;
+
+/** Restore the database from a compressed series of protobuf objects. */
+public class Restore extends SiteProgram {
+ private static final String OK_FLAG =
+ "--yes-really-import-and-destroy-current-data";
+
+ private final LifecycleManager manager = new LifecycleManager();
+ private Injector dbInjector;
+
+ @Option(name = "--input", aliases = {"-i"}, required = true, metaVar = "FILE", usage = "File to read backup from")
+ private File input;
+
+ @Option(name = OK_FLAG)
+ private boolean run;
+
+ @Inject
+ private SchemaFactory<ReviewDb> dstdb;
+
+ @Override
+ public int run() throws Exception {
+ mustHaveValidSite();
+
+ if (!run) {
+ throw die("Must pass " + OK_FLAG);
+ }
+
+ dbInjector = createDbInjector(SINGLE_USER);
+ manager.add(dbInjector);
+ manager.start();
+ dbInjector.injectMembers(this);
+
+ ReviewDb dst = dstdb.open();
+ try {
+ FileInputStream in = new FileInputStream(input);
+ try {
+ RestoreBackup.restore(in, dst);
+ } finally {
+ in.close();
+ }
+ System.err.println("Restore completed");
+ } finally {
+ dst.close();
+ }
+ return 0;
+ }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index e9d0f2e..27b44cf 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -27,6 +27,7 @@
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.servlet.GuiceFilter;
+import com.google.inject.servlet.GuiceHelper;
import com.google.inject.servlet.GuiceServletContextListener;
import org.eclipse.jetty.io.EndPoint;
@@ -65,6 +66,10 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
@Singleton
public class JettyServer {
static class Lifecycle implements LifecycleListener {
@@ -115,7 +120,23 @@
Handler app = makeContext(env, cfg);
if (cfg.getBoolean("httpd", "requestlog", !reverseProxy)) {
- RequestLogHandler handler = new RequestLogHandler();
+ RequestLogHandler handler = new RequestLogHandler() {
+ @Override
+ public void handle(String target, Request baseRequest,
+ final HttpServletRequest req, final HttpServletResponse rsp)
+ throws IOException, ServletException {
+ // Force the user to construct, so its available to our HttpLog
+ // later on when the request gets logged out to the access file.
+ //
+ GuiceHelper.runInContext(req, rsp, new Runnable() {
+ @Override
+ public void run() {
+ userProvider.get();
+ }
+ });
+ super.handle(target, baseRequest, req, rsp);
+ }
+ };
handler.setRequestLog(new HttpLog(site, userProvider));
handler.setHandler(app);
app = handler;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
index d501ea5..0f426cf 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitDatabase.java
@@ -57,6 +57,7 @@
final boolean userPassAuth;
switch (db_type) {
+ case NOSQL_HEAP_FILE:
case H2: {
userPassAuth = false;
String path = database.get("database");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
index 482405e..bfc0eaf 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSshd.java
@@ -54,13 +54,20 @@
String hostname = "*";
int port = 29418;
String listenAddress = sshd.get("listenAddress");
- if (listenAddress != null && !listenAddress.isEmpty()) {
+ if (isOff(listenAddress)) {
+ hostname = "off";
+ } else if (listenAddress != null && !listenAddress.isEmpty()) {
final InetSocketAddress addr = SocketUtil.parse(listenAddress, port);
hostname = SocketUtil.hostname(addr);
port = addr.getPort();
}
hostname = ui.readString(hostname, "Listen on address");
+ if (isOff(hostname)) {
+ sshd.set("listenAddress", "off");
+ return;
+ }
+
port = ui.readInt(port, "Listen on port");
sshd.set("listenAddress", SocketUtil.format(hostname, port));
@@ -73,6 +80,12 @@
generateSshHostKeys();
}
+ private static boolean isOff(String listenHostname) {
+ return "off".equalsIgnoreCase(listenHostname)
+ || "none".equalsIgnoreCase(listenHostname)
+ || "no".equalsIgnoreCase(listenHostname);
+ }
+
private void generateSshHostKeys() throws InterruptedException, IOException {
if (!site.ssh_key.exists() //
&& !site.ssh_rsa.exists() //
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
index e5ca2c6..f9985a8 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
@@ -18,17 +18,20 @@
import static com.google.inject.Stage.PRODUCTION;
import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.config.GerritServerConfigModule;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.schema.DataSourceProvider;
-import com.google.gerrit.server.schema.DatabaseModule;
+import com.google.gerrit.server.schema.SchemaVersion;
import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.AbstractModule;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
import com.google.inject.spi.Message;
@@ -156,13 +159,13 @@
@Override
protected void configure() {
bind(DataSourceProvider.Context.class).toInstance(context);
- bind(Key.get(DataSource.class, Names.named("ReviewDb"))).toProvider(
+ bind(new TypeLiteral<SchemaFactory<ReviewDb>>() {}).toProvider(
DataSourceProvider.class).in(SINGLETON);
listener().to(DataSourceProvider.class);
}
});
modules.add(new GerritServerConfigModule());
- modules.add(new DatabaseModule());
+ modules.add(new SchemaVersion.Module());
try {
return Guice.createInjector(PRODUCTION, modules);
diff --git a/gerrit-prettify/pom.xml b/gerrit-prettify/pom.xml
index 060ffdd..18ba674 100644
--- a/gerrit-prettify/pom.xml
+++ b/gerrit-prettify/pom.xml
@@ -55,5 +55,10 @@
<artifactId>gwt-user</artifactId>
<scope>provided</scope>
</dependency>
+
+ <dependency>
+ <groupId>gwtorm</groupId>
+ <artifactId>gwtorm</artifactId>
+ </dependency>
</dependencies>
</project>
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/BaseEdit.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/BaseEdit.java
new file mode 100644
index 0000000..df3d216
--- /dev/null
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/BaseEdit.java
@@ -0,0 +1,79 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.prettify.common;
+
+import com.google.gwtorm.client.Column;
+
+import org.eclipse.jgit.diff.Edit;
+import org.eclipse.jgit.diff.Edit.Type;
+
+public class BaseEdit {
+ @Column(id = 1)
+ protected int beginA;
+
+ @Column(id = 2)
+ protected int endA;
+
+ @Column(id = 3)
+ protected int beginB;
+
+ @Column(id = 4)
+ protected int endB;
+
+ protected BaseEdit() {
+ }
+
+ public BaseEdit(int beginA, int endA, int beginB, int endB) {
+ this.beginA = beginA;
+ this.endA = endA;
+ this.beginB = beginB;
+ this.endB = endB;
+ }
+
+ public BaseEdit(Edit edit) {
+ this(edit.getBeginA(), edit.getEndA(), edit.getBeginB(), edit.getEndB());
+ }
+
+ public int getBeginA() {
+ return beginA;
+ }
+
+ public int getEndA() {
+ return endA;
+ }
+
+ public int getBeginB() {
+ return beginB;
+ }
+
+ public int getEndB() {
+ return endB;
+ }
+
+ public final Type getType() {
+ if (beginA == endA) {
+ if (beginB < endB) {
+ return Type.INSERT;
+ }
+ if (beginB == endB) {
+ return Type.EMPTY;
+ }
+ }
+ if (beginA < endA && beginB == endB) {
+ return Type.DELETE;
+ }
+ return Type.REPLACE;
+ }
+}
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/EditList.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/EditList.java
index d41865a..1fd647c 100644
--- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/EditList.java
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/EditList.java
@@ -14,18 +14,16 @@
package com.google.gerrit.prettify.common;
-import org.eclipse.jgit.diff.Edit;
-
import java.util.Iterator;
import java.util.List;
public class EditList {
- private final List<Edit> edits;
+ private final List<LineEdit> edits;
private final int context;
private final int aSize;
private final int bSize;
- public EditList(final List<Edit> edits, final int contextLines,
+ public EditList(final List<LineEdit> edits, final int contextLines,
final int aSize, final int bSize) {
this.edits = edits;
this.context = contextLines;
@@ -33,7 +31,7 @@
this.bSize = bSize;
}
- public List<Edit> getEdits() {
+ public List<LineEdit> getEdits() {
return edits;
}
@@ -70,8 +68,8 @@
}
private boolean combineA(final int i) {
- final Edit s = edits.get(i);
- final Edit e = edits.get(i - 1);
+ final BaseEdit s = edits.get(i);
+ final BaseEdit e = edits.get(i - 1);
return s.getBeginA() - e.getEndA() <= 2 * context;
}
@@ -83,9 +81,9 @@
public class Hunk {
private int curIdx;
- private Edit curEdit;
+ private BaseEdit curEdit;
private final int endIdx;
- private final Edit endEdit;
+ private final BaseEdit endEdit;
private int aCur;
private int bCur;
@@ -112,7 +110,7 @@
return bCur;
}
- public Edit getCurEdit() {
+ public BaseEdit getCurEdit() {
return curEdit;
}
@@ -166,7 +164,7 @@
return aCur < aEnd || bCur < bEnd;
}
- private boolean in(final Edit edit) {
+ private boolean in(final BaseEdit edit) {
return aCur < edit.getEndA() || bCur < edit.getEndB();
}
}
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/LineEdit.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/LineEdit.java
new file mode 100644
index 0000000..ea77218
--- /dev/null
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/LineEdit.java
@@ -0,0 +1,58 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.prettify.common;
+
+import com.google.gwtorm.client.Column;
+
+import org.eclipse.jgit.diff.Edit;
+
+import java.util.List;
+
+public class LineEdit extends BaseEdit {
+ @Column(id = 5)
+ protected List<BaseEdit> edits;
+
+ public LineEdit(int beginA, int endA, int beginB, int endB,
+ List<BaseEdit> edits) {
+ this.beginA = beginA;
+ this.endA = endA;
+ this.beginB = beginB;
+ this.endB = endB;
+ this.edits = edits;
+ }
+
+ protected LineEdit() {
+ }
+
+ public LineEdit(Edit edit) {
+ this(edit.getBeginA(), edit.getEndA(), edit.getBeginB(), edit.getEndB());
+ }
+
+ public LineEdit(BaseEdit edit, List<BaseEdit> edits) {
+ this(edit.beginA, edit.endA, edit.beginB, edit.endB, edits);
+ }
+
+ public LineEdit(int beginA, int endA, int beginB, int endB) {
+ this(beginA, endA, beginB, endB, null);
+ }
+
+ public LineEdit(int beginA, int beginB) {
+ this(beginA, beginA, beginB, beginB);
+ }
+
+ public List<BaseEdit> getEdits() {
+ return edits;
+ }
+}
diff --git a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/Edit_JsonSerializer.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/LineEdit_JsonSerializer.java
similarity index 69%
rename from gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/Edit_JsonSerializer.java
rename to gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/LineEdit_JsonSerializer.java
index 7870002..ae6e3d8 100644
--- a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/Edit_JsonSerializer.java
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/LineEdit_JsonSerializer.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package org.eclipse.jgit.diff;
+package com.google.gerrit.prettify.common;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwtjsonrpc.client.impl.JsonSerializer;
@@ -20,11 +20,11 @@
import java.util.ArrayList;
import java.util.List;
-public class Edit_JsonSerializer extends JsonSerializer<Edit> {
- public static final Edit_JsonSerializer INSTANCE = new Edit_JsonSerializer();
+public class LineEdit_JsonSerializer extends JsonSerializer<LineEdit> {
+ public static final LineEdit_JsonSerializer INSTANCE = new LineEdit_JsonSerializer();
@Override
- public Edit fromJson(Object jso) {
+ public LineEdit fromJson(Object jso) {
if (jso == null) {
return null;
}
@@ -32,26 +32,26 @@
final JavaScriptObject o = (JavaScriptObject) jso;
final int cnt = length(o);
if (4 == cnt) {
- return new Edit(get(o, 0), get(o, 1), get(o, 2), get(o, 3));
+ return new LineEdit(get(o, 0), get(o, 1), get(o, 2), get(o, 3));
}
- List<Edit> l = new ArrayList<Edit>((cnt / 4) - 1);
+ List<BaseEdit> l = new ArrayList<BaseEdit>((cnt / 4) - 1);
for (int i = 4; i < cnt;) {
int as = get(o, i++);
int ae = get(o, i++);
int bs = get(o, i++);
int be = get(o, i++);
- l.add(new Edit(as, ae, bs, be));
+ l.add(new BaseEdit(as, ae, bs, be));
}
- return new ReplaceEdit(get(o, 0), get(o, 1), get(o, 2), get(o, 3), l);
+ return new LineEdit(get(o, 0), get(o, 1), get(o, 2), get(o, 3), l);
}
@Override
- public void printJson(final StringBuilder sb, final Edit o) {
+ public void printJson(final StringBuilder sb, final LineEdit o) {
sb.append('[');
append(sb, o);
- if (o instanceof ReplaceEdit) {
- for (Edit e : ((ReplaceEdit) o).getInternalEdits()) {
+ if (o.getEdits() != null) {
+ for (BaseEdit e : o.getEdits()) {
sb.append(',');
append(sb, e);
}
@@ -59,7 +59,7 @@
sb.append(']');
}
- private void append(final StringBuilder sb, final Edit o) {
+ private void append(final StringBuilder sb, final BaseEdit o) {
sb.append(o.getBeginA());
sb.append(',');
sb.append(o.getEndA());
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettyFormatter.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettyFormatter.java
index 5d1592d..74d903d 100644
--- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettyFormatter.java
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/PrettyFormatter.java
@@ -18,9 +18,6 @@
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
-import org.eclipse.jgit.diff.Edit;
-import org.eclipse.jgit.diff.ReplaceEdit;
-
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -30,9 +27,9 @@
public static abstract class EditFilter {
abstract String getStyleName();
- abstract int getBegin(Edit edit);
+ abstract int getBegin(BaseEdit edit);
- abstract int getEnd(Edit edit);
+ abstract int getEnd(BaseEdit edit);
}
public static final EditFilter A = new EditFilter() {
@@ -42,12 +39,12 @@
}
@Override
- int getBegin(Edit edit) {
+ int getBegin(BaseEdit edit) {
return edit.getBeginA();
}
@Override
- int getEnd(Edit edit) {
+ int getEnd(BaseEdit edit) {
return edit.getEndA();
}
};
@@ -59,19 +56,19 @@
}
@Override
- int getBegin(Edit edit) {
+ int getBegin(BaseEdit edit) {
return edit.getBeginB();
}
@Override
- int getEnd(Edit edit) {
+ int getEnd(BaseEdit edit) {
return edit.getEndB();
}
};
protected SparseFileContent content;
protected EditFilter side;
- protected List<Edit> edits;
+ protected List<LineEdit> edits;
protected AccountDiffPreference diffPrefs;
protected String fileName;
protected Set<Integer> trailingEdits;
@@ -103,7 +100,7 @@
side = f;
}
- public void setEditList(List<Edit> all) {
+ public void setEditList(List<LineEdit> all) {
edits = all;
}
@@ -348,17 +345,17 @@
// in the source. That simplifies our loop below because we'll never
// run off the end of the edit list.
//
- List<Edit> edits = new ArrayList<Edit>(this.edits.size() + 1);
+ List<LineEdit> edits = new ArrayList<LineEdit>(this.edits.size() + 1);
edits.addAll(this.edits);
- edits.add(new Edit(src.size(), src.size()));
+ edits.add(new LineEdit(src.size(), src.size()));
SafeHtmlBuilder buf = new SafeHtmlBuilder();
int curIdx = 0;
- Edit curEdit = edits.get(curIdx);
+ LineEdit curEdit = edits.get(curIdx);
- ReplaceEdit lastReplace = null;
- List<Edit> charEdits = null;
+ LineEdit lastReplace = null;
+ List<BaseEdit> charEdits = null;
int lastPos = 0;
int lastIdx = 0;
@@ -381,10 +378,10 @@
// index occurs within the edit. The line is a modification.
//
- if (curEdit instanceof ReplaceEdit) {
+ if (curEdit.getEdits() != null) {
if (lastReplace != curEdit) {
- lastReplace = (ReplaceEdit) curEdit;
- charEdits = lastReplace.getInternalEdits();
+ lastReplace = curEdit;
+ charEdits = lastReplace.getEdits();
lastPos = 0;
lastIdx = 0;
}
@@ -396,7 +393,7 @@
break;
}
- final Edit edit = charEdits.get(lastIdx);
+ final BaseEdit edit = charEdits.get(lastIdx);
final int b = side.getBegin(edit) - lastPos;
final int e = side.getEnd(edit) - lastPos;
@@ -468,7 +465,7 @@
}
}
- private int compare(int index, Edit edit) {
+ private int compare(int index, BaseEdit edit) {
if (index < side.getBegin(edit)) {
return -1; // index occurs before the edit.
diff --git a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/SparseFileContent.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/SparseFileContent.java
index a5373b8..eb32b17 100644
--- a/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/SparseFileContent.java
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/common/SparseFileContent.java
@@ -14,8 +14,6 @@
package com.google.gerrit.prettify.common;
-import org.eclipse.jgit.diff.Edit;
-
import java.util.ArrayList;
import java.util.List;
@@ -219,7 +217,7 @@
return b.toString();
}
- public SparseFileContent apply(SparseFileContent a, List<Edit> edits) {
+ public SparseFileContent apply(SparseFileContent a, List<LineEdit> edits) {
EditList list = new EditList(edits, size, a.size(), size);
ArrayList<String> lines = new ArrayList<String>(size);
for (final EditList.Hunk hunk : list.getHunks()) {
diff --git a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/server/EditDeserializer.java
similarity index 75%
rename from gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java
rename to gerrit-prettify/src/main/java/com/google/gerrit/prettify/server/EditDeserializer.java
index 1df89b7..fc5af21 100644
--- a/gerrit-patch-jgit/src/main/java/org/eclipse/jgit/diff/EditDeserializer.java
+++ b/gerrit-prettify/src/main/java/com/google/gerrit/prettify/server/EditDeserializer.java
@@ -12,8 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package org.eclipse.jgit.diff;
+package com.google.gerrit.prettify.server;
+import com.google.gerrit.prettify.common.BaseEdit;
+import com.google.gerrit.prettify.common.LineEdit;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
@@ -28,9 +30,9 @@
import java.util.ArrayList;
import java.util.List;
-public class EditDeserializer implements JsonDeserializer<Edit>,
- JsonSerializer<Edit> {
- public Edit deserialize(final JsonElement json, final Type typeOfT,
+public class EditDeserializer implements JsonDeserializer<LineEdit>,
+ JsonSerializer<LineEdit> {
+ public LineEdit deserialize(final JsonElement json, final Type typeOfT,
final JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonNull()) {
return null;
@@ -46,18 +48,18 @@
}
if (4 == cnt) {
- return new Edit(get(o, 0), get(o, 1), get(o, 2), get(o, 3));
+ return new LineEdit(get(o, 0), get(o, 1), get(o, 2), get(o, 3));
}
- List<Edit> l = new ArrayList<Edit>((cnt / 4) - 1);
+ List<BaseEdit> l = new ArrayList<BaseEdit>((cnt / 4) - 1);
for (int i = 4; i < cnt;) {
int as = get(o, i++);
int ae = get(o, i++);
int bs = get(o, i++);
int be = get(o, i++);
- l.add(new Edit(as, ae, bs, be));
+ l.add(new BaseEdit(as, ae, bs, be));
}
- return new ReplaceEdit(get(o, 0), get(o, 1), get(o, 2), get(o, 3), l);
+ return new LineEdit(get(o, 0), get(o, 1), get(o, 2), get(o, 3), l);
}
private static int get(final JsonArray a, final int idx)
@@ -73,22 +75,22 @@
return p.getAsInt();
}
- public JsonElement serialize(final Edit src, final Type typeOfSrc,
+ public JsonElement serialize(final LineEdit src, final Type typeOfSrc,
final JsonSerializationContext context) {
if (src == null) {
return new JsonNull();
}
final JsonArray a = new JsonArray();
add(a, src);
- if (src instanceof ReplaceEdit) {
- for (Edit e : ((ReplaceEdit) src).getInternalEdits()) {
+ if (src.getEdits() != null) {
+ for (BaseEdit e : src.getEdits()) {
add(a, e);
}
}
return a;
}
- private void add(final JsonArray a, final Edit src) {
+ private void add(final JsonArray a, final BaseEdit src) {
a.add(new JsonPrimitive(src.getBeginA()));
a.add(new JsonPrimitive(src.getEndA()));
a.add(new JsonPrimitive(src.getBeginB()));
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AbstractAgreement.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AbstractAgreement.java
index 987fc4b..417cbb9 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AbstractAgreement.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AbstractAgreement.java
@@ -15,9 +15,18 @@
package com.google.gerrit.reviewdb;
import java.sql.Timestamp;
+import java.util.Comparator;
/** Base for {@link AccountAgreement} or {@link AccountGroupAgreement}. */
public interface AbstractAgreement {
+ public static final Comparator<AbstractAgreement> SORT =
+ new Comparator<AbstractAgreement>() {
+ @Override
+ public int compare(AbstractAgreement a, AbstractAgreement b) {
+ return b.getAcceptedOn().compareTo(a.getAcceptedOn());
+ }
+ };
+
public static enum Status {
NEW('n'),
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java
index 43b7b17..ee65c55 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Account.java
@@ -16,6 +16,7 @@
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.IntKey;
+import com.google.gwtorm.client.StringKey;
import java.sql.Timestamp;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountExternalId.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountExternalId.java
index 853ebd5..bc93892 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountExternalId.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountExternalId.java
@@ -39,6 +39,10 @@
/** Very old scheme from Gerrit Code Review 1.x imports. */
public static final String LEGACY_GAE = "Google Account ";
+ public static AccountExternalId.Key forUsername(String username) {
+ return new Key(SCHEME_USERNAME, username);
+ }
+
public static class Key extends StringKey<com.google.gwtorm.client.Key<?>> {
private static final long serialVersionUID = 1L;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountExternalIdAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountExternalIdAccess.java
index 0719035..761a2de 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountExternalIdAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountExternalIdAccess.java
@@ -32,10 +32,6 @@
@Query("WHERE accountId = ?")
ResultSet<AccountExternalId> byAccount(Account.Id id) throws OrmException;
- @Query("WHERE accountId = ? AND emailAddress = ?")
- ResultSet<AccountExternalId> byAccountEmail(Account.Id id, String email)
- throws OrmException;
-
@Query("WHERE emailAddress = ?")
ResultSet<AccountExternalId> byEmailAddress(String email) throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java
index 52bef2b..2913ca3 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/AccountProjectWatch.java
@@ -51,6 +51,10 @@
return accountId;
}
+ public Project.NameKey getProjectName() {
+ return projectName;
+ }
+
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {projectName, filter};
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ActiveSession.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ActiveSession.java
new file mode 100644
index 0000000..f90add5
--- /dev/null
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ActiveSession.java
@@ -0,0 +1,127 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.reviewdb;
+
+import com.google.gwtorm.client.Column;
+import com.google.gwtorm.client.StringKey;
+
+import java.sql.Timestamp;
+
+public final class ActiveSession {
+ public static final class Key extends
+ StringKey<com.google.gwtorm.client.Key<?>> {
+ private static final long serialVersionUID = 1L;
+
+ @Column(id = 1, length = 60)
+ protected String sessionCookie;
+
+ protected Key() {
+ }
+
+ public Key(final String token) {
+ this.sessionCookie = token;
+ }
+
+ @Override
+ public String get() {
+ return sessionCookie;
+ }
+
+ @Override
+ protected void set(String newValue) {
+ sessionCookie = newValue;
+ }
+ }
+
+ @Column(id = 1, name = Column.NONE)
+ protected ActiveSession.Key key;
+
+ @Column(id = 2)
+ protected Account.Id accountId;
+
+ @Column(id = 3)
+ protected Timestamp refreshCookieAt;
+
+ @Column(id = 4)
+ protected boolean persistentCookie;
+
+ @Column(id = 5, notNull = false)
+ protected AccountExternalId.Key externalId;
+
+ @Column(id = 6)
+ protected String xsrfToken;
+
+ @Column(id = 7)
+ protected Timestamp lastSeen;
+
+ protected ActiveSession() {
+ }
+
+ public ActiveSession(final ActiveSession.Key k, final Account.Id accountId,
+ final Timestamp refreshCookieAt, final boolean persistentCookie,
+ final AccountExternalId.Key externalId, final String xsrfToken) {
+ this.key = k;
+ this.accountId = accountId;
+ this.refreshCookieAt = refreshCookieAt;
+ this.persistentCookie = persistentCookie;
+ this.externalId = externalId;
+ this.xsrfToken = xsrfToken;
+ this.lastSeen = now();
+ }
+
+ public Key getKey() {
+ return key;
+ }
+
+ public Timestamp getLastSeen() {
+ return lastSeen;
+ }
+
+ public void updateLastSeen() {
+ lastSeen = now();
+ }
+
+ public Account.Id getAccountId() {
+ return accountId;
+ }
+
+ public Timestamp getRefreshCookieAt() {
+ return refreshCookieAt;
+ }
+
+ public void setRefreshCookieAt(Timestamp refreshCookieAt) {
+ this.refreshCookieAt = refreshCookieAt;
+ }
+
+ public boolean isPersistentCookie() {
+ return persistentCookie;
+ }
+
+ public AccountExternalId.Key getExternalId() {
+ return externalId;
+ }
+
+ public String getXsrfToken() {
+ return xsrfToken;
+ }
+
+ public boolean needsCookieRefresh() {
+ return refreshCookieAt.before(now());
+ }
+
+ private Timestamp now() {
+ return new Timestamp(System.currentTimeMillis());
+ }
+}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ActiveSessionAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ActiveSessionAccess.java
new file mode 100644
index 0000000..6080566
--- /dev/null
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ActiveSessionAccess.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.reviewdb;
+
+import com.google.gwtorm.client.Access;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.PrimaryKey;
+import com.google.gwtorm.client.Query;
+import com.google.gwtorm.client.ResultSet;
+
+public interface ActiveSessionAccess extends
+ Access<ActiveSession, ActiveSession.Key> {
+ @PrimaryKey("key")
+ ActiveSession get(ActiveSession.Key key) throws OrmException;
+
+ @Query
+ ResultSet<ActiveSession> all() throws OrmException;
+}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java
index e4ae63d..0878fc2 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/Change.java
@@ -355,6 +355,10 @@
@Column(id = 14, notNull = false)
protected String topic;
+ /** Max 64 bit unsigned value (0xFFFFFFFFFFFFFFFF) minus {@link #sortKey} */
+ @Column(id = 15, length = 16)
+ protected String sortKeyDesc;
+
protected Change() {
}
@@ -406,8 +410,17 @@
return sortKey;
}
+ public String getSortKeyDesc() {
+ return sortKeyDesc;
+ }
+
public void setSortKey(final String newSortKey) {
sortKey = newSortKey;
+
+ // Since long is signed in Java, we need to use a little two's compliment
+ // trickery to get the same hex value that we would have gotten if we could
+ // do 0xFFFFFFFFFFFFFFFF - sortKey unsigned.
+ sortKeyDesc = Long.toHexString(-1l - Long.parseLong(sortKey, 16));
}
public Account.Id getOwner() {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ChangeAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ChangeAccess.java
index dba2a58..6a29d6c 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ChangeAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ChangeAccess.java
@@ -41,10 +41,10 @@
@Query("WHERE owner = ? AND open = true ORDER BY createdOn, changeId")
ResultSet<Change> byOwnerOpen(Account.Id id) throws OrmException;
- @Query("WHERE owner = ? AND open = false ORDER BY lastUpdatedOn DESC LIMIT 5")
+ @Query("WHERE owner = ? AND open = false ORDER BY sortKeyDesc LIMIT 5")
ResultSet<Change> byOwnerClosed(Account.Id id) throws OrmException;
- @Query("WHERE owner = ? AND open = false ORDER BY lastUpdatedOn")
+ @Query("WHERE owner = ? AND open = false ORDER BY sortKey")
ResultSet<Change> byOwnerClosedAll(Account.Id id) throws OrmException;
@Query("WHERE dest = ? AND status = '" + Change.STATUS_SUBMITTED
@@ -57,7 +57,7 @@
@Query("WHERE open = true AND sortKey > ? ORDER BY sortKey LIMIT ?")
ResultSet<Change> allOpenPrev(String sortKey, int limit) throws OrmException;
- @Query("WHERE open = true AND sortKey < ? ORDER BY sortKey DESC LIMIT ?")
+ @Query("WHERE open = true AND sortKeyDesc > ? ORDER BY sortKeyDesc LIMIT ?")
ResultSet<Change> allOpenNext(String sortKey, int limit) throws OrmException;
@Query("WHERE open = true AND dest.projectName = ?")
@@ -68,8 +68,8 @@
ResultSet<Change> byProjectOpenPrev(Project.NameKey p, String sortKey,
int limit) throws OrmException;
- @Query("WHERE open = true AND dest.projectName = ? AND sortKey < ?"
- + " ORDER BY sortKey DESC LIMIT ?")
+ @Query("WHERE open = true AND dest.projectName = ? AND sortKeyDesc > ?"
+ + " ORDER BY sortKeyDesc LIMIT ?")
ResultSet<Change> byProjectOpenNext(Project.NameKey p, String sortKey,
int limit) throws OrmException;
@@ -78,8 +78,8 @@
ResultSet<Change> byProjectClosedPrev(char status, Project.NameKey p,
String sortKey, int limit) throws OrmException;
- @Query("WHERE open = false AND status = ? AND dest.projectName = ? AND sortKey < ?"
- + " ORDER BY sortKey DESC LIMIT ?")
+ @Query("WHERE open = false AND status = ? AND dest.projectName = ? AND sortKeyDesc > ?"
+ + " ORDER BY sortKeyDesc LIMIT ?")
ResultSet<Change> byProjectClosedNext(char status, Project.NameKey p,
String sortKey, int limit) throws OrmException;
@@ -87,7 +87,7 @@
ResultSet<Change> allClosedPrev(char status, String sortKey, int limit)
throws OrmException;
- @Query("WHERE open = false AND status = ? AND sortKey < ? ORDER BY sortKey DESC LIMIT ?")
+ @Query("WHERE open = false AND status = ? AND sortKeyDesc > ? ORDER BY sortKeyDesc LIMIT ?")
ResultSet<Change> allClosedNext(char status, String sortKey, int limit)
throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchLineCommentAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchLineCommentAccess.java
index 26785a8..936519b 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchLineCommentAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchLineCommentAccess.java
@@ -28,38 +28,28 @@
@Query("WHERE key.patchKey.patchSetId.changeId = ?")
ResultSet<PatchLineComment> byChange(Change.Id id) throws OrmException;
- @Query("WHERE key.patchKey = ? AND status = '"
- + PatchLineComment.STATUS_PUBLISHED + "' ORDER BY lineNbr,writtenOn")
- ResultSet<PatchLineComment> published(Patch.Key patch) throws OrmException;
-
@Query("WHERE key.patchKey.patchSetId.changeId = ?"
+ " AND key.patchKey.fileName = ? AND status = '"
+ PatchLineComment.STATUS_PUBLISHED + "' ORDER BY lineNbr,writtenOn")
- ResultSet<PatchLineComment> published(Change.Id id, String file)
+ ResultSet<PatchLineComment> publishedByChangeFile(Change.Id id, String file)
throws OrmException;
@Query("WHERE key.patchKey.patchSetId = ? AND status = '"
+ PatchLineComment.STATUS_PUBLISHED + "'")
- ResultSet<PatchLineComment> published(PatchSet.Id patchset)
+ ResultSet<PatchLineComment> publishedByPatchSet(PatchSet.Id patchset)
throws OrmException;
@Query("WHERE key.patchKey.patchSetId = ? AND status = '"
+ PatchLineComment.STATUS_DRAFT
+ "' AND author = ? ORDER BY key.patchKey,lineNbr,writtenOn")
- ResultSet<PatchLineComment> draft(PatchSet.Id patchset, Account.Id author)
- throws OrmException;
-
- @Query("WHERE key.patchKey = ? AND status = '"
- + PatchLineComment.STATUS_DRAFT
- + "' AND author = ? ORDER BY lineNbr,writtenOn")
- ResultSet<PatchLineComment> draft(Patch.Key patch, Account.Id author)
- throws OrmException;
+ ResultSet<PatchLineComment> draftByPatchSet(PatchSet.Id patchset,
+ Account.Id author) throws OrmException;
@Query("WHERE key.patchKey.patchSetId.changeId = ?"
+ " AND key.patchKey.fileName = ? AND author = ? AND status = '"
+ PatchLineComment.STATUS_DRAFT + "' ORDER BY lineNbr,writtenOn")
- ResultSet<PatchLineComment> draft(Change.Id id, String file, Account.Id author)
- throws OrmException;
+ ResultSet<PatchLineComment> draftByChangeFile(Change.Id id, String file,
+ Account.Id author) throws OrmException;
@Query("WHERE status = '" + PatchLineComment.STATUS_DRAFT
+ "' AND author = ?")
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetApproval.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetApproval.java
index 341d085..e53df6f 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetApproval.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetApproval.java
@@ -88,6 +88,10 @@
@Column(id = 5, length = 16, notNull = false)
protected String changeSortKey;
+ /** <i>Cached copy of Change.sortKeyDesc</i>; only if {@link #changeOpen} = false */
+ @Column(id = 6, length = 16, notNull = false)
+ protected String changeSortKeyDesc;
+
protected PatchSetApproval() {
}
@@ -141,5 +145,6 @@
public void cache(final Change c) {
changeOpen = c.open;
changeSortKey = c.sortKey;
+ changeSortKeyDesc = c.sortKeyDesc;
}
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetApprovalAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetApprovalAccess.java
index 417d264..1ab03a8 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetApprovalAccess.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/PatchSetApprovalAccess.java
@@ -40,7 +40,7 @@
throws OrmException;
@Query("WHERE changeOpen = false AND key.accountId = ?"
- + " ORDER BY changeSortKey DESC LIMIT 10")
+ + " ORDER BY changeSortKeyDesc LIMIT 10")
ResultSet<PatchSetApproval> closedByUser(Account.Id account)
throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java
index f9b3cfa..9b7e9e4 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java
@@ -33,87 +33,90 @@
public interface ReviewDb extends Schema {
/* If you change anything, update SchemaVersion.C to use a new version. */
- @Relation
+ @Relation(id = 1)
SchemaVersionAccess schemaVersion();
- @Relation
+ @Relation(id = 2)
SystemConfigAccess systemConfig();
- @Relation
+ @Relation(id = 3)
ApprovalCategoryAccess approvalCategories();
- @Relation
+ @Relation(id = 4)
ApprovalCategoryValueAccess approvalCategoryValues();
- @Relation
+ @Relation(id = 5)
ContributorAgreementAccess contributorAgreements();
- @Relation
+ @Relation(id = 6)
AccountAccess accounts();
- @Relation
+ @Relation(id = 7)
AccountExternalIdAccess accountExternalIds();
- @Relation
+ @Relation(id = 8)
AccountSshKeyAccess accountSshKeys();
- @Relation
+ @Relation(id = 9)
AccountAgreementAccess accountAgreements();
- @Relation
+ @Relation(id = 10)
AccountGroupAccess accountGroups();
- @Relation
+ @Relation(id = 11)
AccountGroupNameAccess accountGroupNames();
- @Relation
+ @Relation(id = 12)
AccountGroupMemberAccess accountGroupMembers();
- @Relation
+ @Relation(id = 13)
AccountGroupMemberAuditAccess accountGroupMembersAudit();
- @Relation
+ @Relation(id = 14)
AccountGroupAgreementAccess accountGroupAgreements();
- @Relation
+ @Relation(id = 15)
AccountDiffPreferenceAccess accountDiffPreferences();
- @Relation
+ @Relation(id = 16)
StarredChangeAccess starredChanges();
- @Relation
+ @Relation(id = 17)
AccountProjectWatchAccess accountProjectWatches();
- @Relation
+ @Relation(id = 18)
AccountPatchReviewAccess accountPatchReviews();
- @Relation
+ @Relation(id = 19)
ProjectAccess projects();
- @Relation
+ @Relation(id = 20)
ChangeAccess changes();
- @Relation
+ @Relation(id = 21)
PatchSetApprovalAccess patchSetApprovals();
- @Relation
+ @Relation(id = 22)
ChangeMessageAccess changeMessages();
- @Relation
+ @Relation(id = 23)
PatchSetAccess patchSets();
- @Relation
+ @Relation(id = 24)
PatchSetAncestorAccess patchSetAncestors();
- @Relation
+ @Relation(id = 25)
PatchLineCommentAccess patchComments();
- @Relation
+ @Relation(id = 26)
RefRightAccess refRights();
- @Relation
+ @Relation(id = 27)
TrackingIdAccess trackingIds();
+ @Relation(id = 28)
+ ActiveSessionAccess activeSessions();
+
/** Create the next unique id for an {@link Account}. */
@Sequence(startWith = 1000000)
int nextAccountId() throws OrmException;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/StarredChange.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/StarredChange.java
index 7e4359b..8f2dde0 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/StarredChange.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/StarredChange.java
@@ -43,6 +43,10 @@
return accountId;
}
+ public Change.Id getChangeId() {
+ return changeId;
+ }
+
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {changeId};
@@ -66,4 +70,8 @@
public Change.Id getChangeId() {
return key.changeId;
}
+
+ public StarredChange.Key getKey() {
+ return key;
+ }
}
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql
index 0d41729..750b8ab 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql
@@ -89,10 +89,14 @@
-- covers: allOpenPrev, allOpenNext
CREATE INDEX changes_allOpen
ON changes (open, sort_key);
+CREATE INDEX changes_allOpenD
+ON changes (open, sort_key_desc);
-- covers: byProjectOpenPrev, byProjectOpenNext
CREATE INDEX changes_byProjectOpen
ON changes (open, dest_project_name, sort_key);
+CREATE INDEX changes_byProjectOpenD
+ON changes (open, dest_project_name, sort_key_desc);
-- covers: byProject
CREATE INDEX changes_byProject
@@ -101,6 +105,8 @@
-- covers: allClosedPrev, allClosedNext
CREATE INDEX changes_allClosed
ON changes (open, status, sort_key);
+CREATE INDEX changes_allClosedD
+ON changes (open, status, sort_key_desc);
CREATE INDEX changes_key
ON changes (change_key);
@@ -116,6 +122,8 @@
-- covers: closedByUser
CREATE INDEX patch_set_approvals_closedByUser
ON patch_set_approvals (change_open, account_id, change_sort_key);
+CREATE INDEX patch_set_approvals_closedByUserD
+ON patch_set_approvals (change_open, account_id, change_sort_key_desc);
-- *********************************************************************
diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql
index b44351c..d3bda0b 100644
--- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql
+++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql
@@ -132,16 +132,25 @@
CREATE INDEX changes_allOpen
ON changes (sort_key)
WHERE open = 'Y';
+CREATE INDEX changes_allOpenD
+ON changes (sort_key_desc)
+WHERE open = 'Y';
-- covers: byProjectOpenPrev, byProjectOpenNext
CREATE INDEX changes_byProjectOpen
ON changes (dest_project_name, sort_key)
WHERE open = 'Y';
+CREATE INDEX changes_byProjectOpenD
+ON changes (dest_project_name, sort_key_desc)
+WHERE open = 'Y';
-- covers: allClosedPrev, allClosedNext
CREATE INDEX changes_allClosed
ON changes (status, sort_key)
WHERE open = 'N';
+CREATE INDEX changes_allClosedD
+ON changes (status, sort_key_desc)
+WHERE open = 'N';
-- covers: byProject
CREATE INDEX changes_byProject
@@ -163,6 +172,9 @@
CREATE INDEX patch_set_approvals_closedByUser
ON patch_set_approvals (account_id, change_sort_key)
WHERE change_open = 'N';
+CREATE INDEX patch_set_approvals_closedByUserD
+ON patch_set_approvals (account_id, change_sort_key_desc)
+WHERE change_open = 'N';
-- *********************************************************************
diff --git a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/ChangeTest.java b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/ChangeTest.java
new file mode 100644
index 0000000..42f7401
--- /dev/null
+++ b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/ChangeTest.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.reviewdb;
+
+import junit.framework.TestCase;
+
+public class ChangeTest extends TestCase {
+
+ public void testSortKeyDesc() {
+ Change c = new Change();
+
+ c.setSortKey("000d4ad500003a36");
+ assertEquals("fff2b52affffc5c9", c.sortKeyDesc.toLowerCase());
+
+ c.setSortKey("0000000000000000");
+ assertEquals("ffffffffffffffff", c.sortKeyDesc.toLowerCase());
+ }
+
+}
diff --git a/gerrit-server/pom.xml b/gerrit-server/pom.xml
index 0a9ece3..d056ab3 100644
--- a/gerrit-server/pom.xml
+++ b/gerrit-server/pom.xml
@@ -54,11 +54,6 @@
</dependency>
<dependency>
- <groupId>net.sf.ehcache</groupId>
- <artifactId>ehcache-core</artifactId>
- </dependency>
-
- <dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
</dependency>
@@ -116,6 +111,11 @@
</dependency>
<dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-common</artifactId>
<version>${project.version}</version>
@@ -140,6 +140,11 @@
</dependency>
<dependency>
+ <groupId>com.google.protobuf</groupId>
+ <artifactId>protobuf-java</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
index 0ba85d1..76130a1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
@@ -23,7 +23,6 @@
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.events.ApprovalAttribute;
@@ -39,8 +38,10 @@
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
@@ -184,11 +185,12 @@
*/
public void doPatchsetCreatedHook(final Change change, final PatchSet patchSet) {
final PatchSetCreatedEvent event = new PatchSetCreatedEvent();
- final AccountState uploader = accountCache.get(patchSet.getUploader());
+ final Account uploader =
+ FutureUtil.get(accountCache.getAccount(patchSet.getUploader()));
event.change = eventFactory.asChangeAttribute(change);
event.patchSet = eventFactory.asPatchSetAttribute(patchSet);
- event.uploader = eventFactory.asAccountAttribute(uploader.getAccount());
+ event.uploader = eventFactory.asAccountAttribute(uploader);
fireEvent(change, event);
final List<String> args = new ArrayList<String>();
@@ -203,7 +205,7 @@
args.add("--branch");
args.add(event.change.branch);
args.add("--uploader");
- args.add(getDisplayName(uploader.getAccount()));
+ args.add(getDisplayName(uploader));
args.add("--commit");
args.add(event.patchSet.revision);
args.add("--patchset");
@@ -375,7 +377,7 @@
}
private boolean isVisibleTo(Change change, IdentifiedUser user) {
- final ProjectState pe = projectCache.get(change.getProject());
+ ProjectState pe = FutureUtil.getOrNull(projectCache.get(change.getProject()));
if (pe == null) {
return false;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index 36fc2ff..369532f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -210,4 +210,36 @@
dst.setCharAt(o--, '0');
}
}
+
+ public static String invertSortKey(String sk) {
+ if (sk.equalsIgnoreCase("z")) {
+ return "/"; // The character before '0'
+ } else if (sk.equalsIgnoreCase("/")) {
+ return "z";
+ }
+
+ StringBuilder inv = new StringBuilder(16);
+ inv.setLength(16);
+ formatHexLong(inv, -1l - parseUnsignedHex(sk));
+
+ return inv.toString();
+ }
+
+ private static void formatHexLong(final StringBuilder dst, long l) {
+ int o = 15;
+ while (o >= 0 && l != 0) {
+ dst.setCharAt(o--, hexchar[(int) (l & 0xf)]);
+ l >>>= 4;
+ }
+ while (o >= 0) {
+ dst.setCharAt(o--, '0');
+ }
+ }
+
+ private static long parseUnsignedHex(String s) {
+ final long h = Long.parseLong(s.substring(0, 1), 16);
+ final long l = Long.parseLong(s.substring(1), 16);
+
+ return (h << 60) | l;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index 78cbed3..928ecf1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -22,11 +22,13 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.StarredChange;
import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountDiffPreferencesCache;
+import com.google.gerrit.server.account.AccountProjectWatchCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gwtorm.client.OrmException;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.inject.Inject;
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
@@ -34,8 +36,6 @@
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.util.SystemReader;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -46,7 +46,6 @@
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
-import java.util.List;
import java.util.Set;
import java.util.TimeZone;
@@ -61,15 +60,24 @@
private final Provider<String> canonicalUrl;
private final Realm realm;
private final AccountCache accountCache;
+ private final StarredChangesCache starredChangesCache;
+ private final AccountProjectWatchCache accountProjectWatchCache;
+ private final AccountDiffPreferencesCache accountDiffPreferencesCache;
@Inject
GenericFactory(final AuthConfig authConfig,
final @CanonicalWebUrl Provider<String> canonicalUrl,
- final Realm realm, final AccountCache accountCache) {
+ final Realm realm, final AccountCache accountCache,
+ final StarredChangesCache starredChangesCache,
+ final AccountProjectWatchCache accountProjectWatchCache,
+ final AccountDiffPreferencesCache accountDiffPreferencesCache) {
this.authConfig = authConfig;
this.canonicalUrl = canonicalUrl;
this.realm = realm;
this.accountCache = accountCache;
+ this.starredChangesCache = starredChangesCache;
+ this.accountProjectWatchCache = accountProjectWatchCache;
+ this.accountDiffPreferencesCache = accountDiffPreferencesCache;
}
public IdentifiedUser create(final Account.Id id) {
@@ -78,13 +86,15 @@
public IdentifiedUser create(Provider<ReviewDb> db, Account.Id id) {
return new IdentifiedUser(AccessPath.UNKNOWN, authConfig, canonicalUrl,
- realm, accountCache, null, db, id);
+ realm, accountCache, starredChangesCache, accountProjectWatchCache,
+ accountDiffPreferencesCache, null, db, id);
}
public IdentifiedUser create(AccessPath accessPath,
Provider<SocketAddress> remotePeerProvider, Account.Id id) {
return new IdentifiedUser(accessPath, authConfig, canonicalUrl, realm,
- accountCache, remotePeerProvider, null, id);
+ accountCache, starredChangesCache, accountProjectWatchCache,
+ accountDiffPreferencesCache, remotePeerProvider, null, id);
}
}
@@ -100,6 +110,9 @@
private final Provider<String> canonicalUrl;
private final Realm realm;
private final AccountCache accountCache;
+ private final StarredChangesCache starredChangesCache;
+ private final AccountProjectWatchCache accountProjectWatchCache;
+ private final AccountDiffPreferencesCache accountDiffPreferencesCache;
private final Provider<SocketAddress> remotePeerProvider;
private final Provider<ReviewDb> dbProvider;
@@ -108,13 +121,18 @@
RequestFactory(final AuthConfig authConfig,
final @CanonicalWebUrl Provider<String> canonicalUrl,
final Realm realm, final AccountCache accountCache,
-
+ final StarredChangesCache starredChangesCache,
+ final AccountProjectWatchCache accountProjectWatchCache,
+ final AccountDiffPreferencesCache accountDiffPreferencesCache,
final @RemotePeer Provider<SocketAddress> remotePeerProvider,
final Provider<ReviewDb> dbProvider) {
this.authConfig = authConfig;
this.canonicalUrl = canonicalUrl;
this.realm = realm;
this.accountCache = accountCache;
+ this.starredChangesCache = starredChangesCache;
+ this.accountProjectWatchCache = accountProjectWatchCache;
+ this.accountDiffPreferencesCache = accountDiffPreferencesCache;
this.remotePeerProvider = remotePeerProvider;
this.dbProvider = dbProvider;
@@ -123,16 +141,17 @@
public IdentifiedUser create(final AccessPath accessPath,
final Account.Id id) {
return new IdentifiedUser(accessPath, authConfig, canonicalUrl, realm,
- accountCache, remotePeerProvider, dbProvider, id);
+ accountCache, starredChangesCache, accountProjectWatchCache,
+ accountDiffPreferencesCache, remotePeerProvider, dbProvider, id);
}
}
- private static final Logger log =
- LoggerFactory.getLogger(IdentifiedUser.class);
-
private final Provider<String> canonicalUrl;
private final Realm realm;
private final AccountCache accountCache;
+ private final StarredChangesCache starredChangesCache;
+ private final AccountProjectWatchCache accountProjectWatchCache;
+ private final AccountDiffPreferencesCache accountDiffPreferencesCache;
@Nullable
private final Provider<SocketAddress> remotePeerProvider;
@@ -151,20 +170,27 @@
private IdentifiedUser(final AccessPath accessPath,
final AuthConfig authConfig, final Provider<String> canonicalUrl,
final Realm realm, final AccountCache accountCache,
+ final StarredChangesCache starredChangesCache,
+ final AccountProjectWatchCache accountProjectWatchCache,
+ final AccountDiffPreferencesCache accountDiffPreferencesCache,
@Nullable final Provider<SocketAddress> remotePeerProvider,
@Nullable final Provider<ReviewDb> dbProvider, final Account.Id id) {
super(accessPath, authConfig);
this.canonicalUrl = canonicalUrl;
this.realm = realm;
this.accountCache = accountCache;
+ this.starredChangesCache = starredChangesCache;
+ this.accountProjectWatchCache = accountProjectWatchCache;
+ this.accountDiffPreferencesCache = accountDiffPreferencesCache;
this.remotePeerProvider = remotePeerProvider;
this.dbProvider = dbProvider;
this.accountId = id;
}
- private AccountState state() {
+ /** The account state of this user, caching most of their information. */
+ public AccountState getAccountState() {
if (state == null) {
- state = accountCache.get(getAccountId());
+ state = FutureUtil.get(accountCache.get(getAccountId()));
}
return state;
}
@@ -176,30 +202,20 @@
/** @return the user's user name; null if one has not been selected/assigned. */
public String getUserName() {
- return state().getUserName();
+ return getAccountState().getUserName();
}
public Account getAccount() {
- return state().getAccount();
+ return getAccountState().getAccount();
}
public AccountDiffPreference getAccountDiffPreference() {
- AccountDiffPreference diffPref;
- try {
- diffPref = dbProvider.get().accountDiffPreferences().get(getAccountId());
- if (diffPref == null) {
- diffPref = AccountDiffPreference.createDefault(getAccountId());
- }
- } catch (OrmException e) {
- log.warn("Cannot query account diff preferences", e);
- diffPref = AccountDiffPreference.createDefault(getAccountId());
- }
- return diffPref;
+ return FutureUtil.get(accountDiffPreferencesCache.get(getAccountId()));
}
public Set<String> getEmailAddresses() {
if (emailAddresses == null) {
- emailAddresses = state().getEmailAddresses();
+ emailAddresses = getAccountState().getEmailAddresses();
}
return emailAddresses;
}
@@ -207,8 +223,8 @@
@Override
public Set<AccountGroup.Id> getEffectiveGroups() {
if (effectiveGroups == null) {
- if (authConfig.isIdentityTrustable(state().getExternalIds())) {
- effectiveGroups = realm.groups(state());
+ if (authConfig.isIdentityTrustable(getAccountState().getExternalIds())) {
+ effectiveGroups = realm.groups(getAccountState());
} else {
effectiveGroups = authConfig.getRegisteredGroups();
@@ -224,13 +240,9 @@
throw new OutOfScopeException("Not in request scoped user");
}
final Set<Change.Id> h = new HashSet<Change.Id>();
- try {
- for (final StarredChange sc : dbProvider.get().starredChanges()
- .byAccount(getAccountId())) {
- h.add(sc.getChangeId());
- }
- } catch (OrmException e) {
- log.warn("Cannot query starred by user changes", e);
+ for (StarredChange sc : FutureUtil.get(starredChangesCache
+ .byAccount(getAccountId()))) {
+ h.add(sc.getChangeId());
}
starredChanges = Collections.unmodifiableSet(h);
}
@@ -240,18 +252,8 @@
@Override
public Collection<AccountProjectWatch> getNotificationFilters() {
if (notificationFilters == null) {
- if (dbProvider == null) {
- throw new OutOfScopeException("Not in request scoped user");
- }
- List<AccountProjectWatch> r;
- try {
- r = dbProvider.get().accountProjectWatches() //
- .byAccount(getAccountId()).toList();
- } catch (OrmException e) {
- log.warn("Cannot query notification filters of a user", e);
- r = Collections.emptyList();
- }
- notificationFilters = Collections.unmodifiableList(r);
+ notificationFilters =
+ FutureUtil.get(accountProjectWatchCache.byAccount(getAccountId()));
}
return notificationFilters;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/RequestCleanup.java b/gerrit-server/src/main/java/com/google/gerrit/server/RequestCleanup.java
index 23c0a6f..40bb3ea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/RequestCleanup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/RequestCleanup.java
@@ -33,16 +33,21 @@
LoggerFactory.getLogger(RequestCleanup.class);
private final List<Runnable> cleanup = new LinkedList<Runnable>();
+ private boolean run;
/** Register a task to be completed after the request ends. */
public void add(final Runnable task) {
synchronized (cleanup) {
+ if (run) {
+ throw new IllegalStateException("Request has already been cleaned up");
+ }
cleanup.add(task);
}
}
public void run() {
synchronized (cleanup) {
+ run = true;
for (final Iterator<Runnable> i = cleanup.iterator(); i.hasNext();) {
try {
i.next().run();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesCache.java
new file mode 100644
index 0000000..cfa9554
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesCache.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.StarredChange;
+
+import java.util.List;
+
+public interface StarredChangesCache {
+ public ListenableFuture<List<StarredChange>> byAccount(Account.Id id);
+
+ public ListenableFuture<List<StarredChange>> byChange(Change.Id id);
+
+ public ListenableFuture<Void> evictAsync(StarredChange.Key key);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesCacheImpl.java
new file mode 100644
index 0000000..bfc8e64
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesCacheImpl.java
@@ -0,0 +1,148 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server;
+
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.reviewdb.StarredChange;
+import com.google.gerrit.server.cache.Cache;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.cache.EntryCreator;
+import com.google.gerrit.server.util.CompoundFuture;
+import com.google.gwtorm.client.Column;
+import com.google.gwtorm.client.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+
+import java.util.List;
+
+@Singleton
+public class StarredChangesCacheImpl implements StarredChangesCache {
+ private static final String BY_ACCOUNT_ID = "starred_user";
+ private static final String BY_CHANGE_ID = "starred_change";
+
+ protected static class StarredChangeList {
+ @Column(id = 1)
+ protected List<StarredChange> list;
+
+ protected StarredChangeList() {
+ }
+
+ public StarredChangeList(List<StarredChange> list) {
+ this.list = list;
+ }
+ }
+
+ public static Module module() {
+ return new CacheModule() {
+ @Override
+ protected void configure() {
+ final TypeLiteral<Cache<Account.Id, StarredChangeList>> byAccountIdType =
+ new TypeLiteral<Cache<Account.Id, StarredChangeList>>() {};
+ cache(byAccountIdType, BY_ACCOUNT_ID).populateWith(
+ ByAccountIdLoader.class);
+
+ final TypeLiteral<Cache<Change.Id, StarredChangeList>> byChangeIdType =
+ new TypeLiteral<Cache<Change.Id, StarredChangeList>>() {};
+ cache(byChangeIdType, BY_CHANGE_ID).populateWith(ByChangeIdLoader.class);
+
+ bind(StarredChangesCacheImpl.class);
+ bind(StarredChangesCache.class).to(StarredChangesCacheImpl.class);
+ }
+ };
+ }
+
+ private static final Function<StarredChangeList, List<StarredChange>> unpack =
+ new Function<StarredChangeList, List<StarredChange>>() {
+ @Override
+ public List<StarredChange> apply(StarredChangeList in) {
+ return in.list;
+ }
+ };
+
+ private final Cache<Account.Id, StarredChangeList> byAccountId;
+ private final Cache<Change.Id, StarredChangeList> byChangeId;
+
+ @Inject
+ StarredChangesCacheImpl(
+ @Named(BY_ACCOUNT_ID) Cache<Account.Id, StarredChangeList> byAccountId,
+ @Named(BY_CHANGE_ID) Cache<Change.Id, StarredChangeList> byChangeId) {
+ this.byAccountId = byAccountId;
+ this.byChangeId = byChangeId;
+ }
+
+ @Override
+ public ListenableFuture<List<StarredChange>> byAccount(Account.Id id) {
+ return Futures.compose(byAccountId.get(id), unpack);
+ }
+
+ @Override
+ public ListenableFuture<List<StarredChange>> byChange(Change.Id id) {
+ return Futures.compose(byChangeId.get(id), unpack);
+ }
+
+ @Override
+ public ListenableFuture<Void> evictAsync(StarredChange.Key key) {
+ return CompoundFuture.wrap(byAccountId.removeAsync(key.getParentKey()),
+ byChangeId.removeAsync(key.getChangeId()));
+ }
+
+ static class ByAccountIdLoader extends
+ EntryCreator<Account.Id, StarredChangeList> {
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ ByAccountIdLoader(SchemaFactory<ReviewDb> schema) {
+ this.schema = schema;
+ }
+
+ @Override
+ public StarredChangeList createEntry(Account.Id id) throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ return new StarredChangeList(db.starredChanges().byAccount(id).toList());
+ } finally {
+ db.close();
+ }
+ }
+ }
+
+ static class ByChangeIdLoader extends
+ EntryCreator<Change.Id, StarredChangeList> {
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ ByChangeIdLoader(SchemaFactory<ReviewDb> schema) {
+ this.schema = schema;
+ }
+
+ @Override
+ public StarredChangeList createEntry(Change.Id id) throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ return new StarredChangeList(db.starredChanges().byChange(id).toList());
+ } finally {
+ db.close();
+ }
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountAgreementsCache.java
similarity index 63%
copy from gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/account/AccountAgreementsCache.java
index 1eb1a4f..1640248 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountAgreementsCache.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,13 +14,14 @@
package com.google.gerrit.server.account;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountAgreement;
-import java.util.Set;
+import java.util.List;
-/** Translates an email address to a set of matching accounts. */
-public interface AccountByEmailCache {
- public Set<Account.Id> get(String email);
+public interface AccountAgreementsCache {
+ public ListenableFuture<List<AccountAgreement>> byAccount(Account.Id id);
- public void evict(String email);
+ public ListenableFuture<Void> evictAsync(AccountAgreement.Key key);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountAgreementsCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountAgreementsCacheImpl.java
new file mode 100644
index 0000000..3463ed6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountAgreementsCacheImpl.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountAgreement;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.cache.Cache;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.cache.EntryCreator;
+import com.google.gwtorm.client.Column;
+import com.google.gwtorm.client.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+
+import java.util.List;
+
+@Singleton
+public class AccountAgreementsCacheImpl implements AccountAgreementsCache {
+ private static final String BY_ACCOUNT_ID = "account_agreements";
+
+ protected static class AccountAgreementsList {
+ @Column(id = 1)
+ protected List<AccountAgreement> list;
+
+ protected AccountAgreementsList() {
+ }
+
+ public AccountAgreementsList(List<AccountAgreement> list) {
+ this.list = list;
+ }
+ }
+
+ public static Module module() {
+ return new CacheModule() {
+ @Override
+ protected void configure() {
+ final TypeLiteral<Cache<Account.Id, AccountAgreementsList>> byAccountIdType =
+ new TypeLiteral<Cache<Account.Id, AccountAgreementsList>>() {};
+ cache(byAccountIdType, BY_ACCOUNT_ID).populateWith(
+ ByAccountIdLoader.class);
+
+ bind(AccountAgreementsCacheImpl.class);
+ bind(AccountAgreementsCache.class).to(AccountAgreementsCacheImpl.class);
+ }
+ };
+ }
+
+ private static final Function<AccountAgreementsList, List<AccountAgreement>> unpack =
+ new Function<AccountAgreementsList, List<AccountAgreement>>() {
+ public List<AccountAgreement> apply(AccountAgreementsList in) {
+ return in.list;
+ }
+ };
+
+ private final Cache<Account.Id, AccountAgreementsList> byAccountId;
+
+ @Inject
+ AccountAgreementsCacheImpl(
+ @Named(BY_ACCOUNT_ID) Cache<Account.Id, AccountAgreementsList> byAccountId) {
+ this.byAccountId = byAccountId;
+ }
+
+ @Override
+ public ListenableFuture<List<AccountAgreement>> byAccount(Account.Id id) {
+ return Futures.compose(byAccountId.get(id), unpack);
+ }
+
+ @Override
+ public ListenableFuture<Void> evictAsync(AccountAgreement.Key key) {
+ return byAccountId.removeAsync(key.getParentKey());
+ }
+
+ static class ByAccountIdLoader extends
+ EntryCreator<Account.Id, AccountAgreementsList> {
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ ByAccountIdLoader(SchemaFactory<ReviewDb> schema) {
+ this.schema = schema;
+ }
+
+ @Override
+ public AccountAgreementsList createEntry(Account.Id id) throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ return new AccountAgreementsList(db.accountAgreements().byAccount(id)
+ .toList());
+ } finally {
+ db.close();
+ }
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java
deleted file mode 100644
index 64046fa..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.account;
-
-import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.reviewdb.AccountExternalId;
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.server.cache.Cache;
-import com.google.gerrit.server.cache.CacheModule;
-import com.google.gerrit.server.cache.EntryCreator;
-import com.google.gwtorm.client.SchemaFactory;
-import com.google.inject.Inject;
-import com.google.inject.Module;
-import com.google.inject.Singleton;
-import com.google.inject.TypeLiteral;
-import com.google.inject.name.Named;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/** Translates an email address to a set of matching accounts. */
-@Singleton
-public class AccountByEmailCacheImpl implements AccountByEmailCache {
- private static final String CACHE_NAME = "accounts_byemail";
-
- public static Module module() {
- return new CacheModule() {
- @Override
- protected void configure() {
- final TypeLiteral<Cache<String, Set<Account.Id>>> type =
- new TypeLiteral<Cache<String, Set<Account.Id>>>() {};
- core(type, CACHE_NAME).populateWith(Loader.class);
- bind(AccountByEmailCacheImpl.class);
- bind(AccountByEmailCache.class).to(AccountByEmailCacheImpl.class);
- }
- };
- }
-
- private final Cache<String, Set<Account.Id>> cache;
-
- @Inject
- AccountByEmailCacheImpl(
- @Named(CACHE_NAME) final Cache<String, Set<Account.Id>> cache) {
- this.cache = cache;
- }
-
- public Set<Account.Id> get(final String email) {
- return cache.get(email);
- }
-
- public void evict(final String email) {
- cache.remove(email);
- }
-
- static class Loader extends EntryCreator<String, Set<Account.Id>> {
- private final SchemaFactory<ReviewDb> schema;
-
- @Inject
- Loader(final SchemaFactory<ReviewDb> schema) {
- this.schema = schema;
- }
-
- @Override
- public Set<Account.Id> createEntry(final String email) throws Exception {
- final ReviewDb db = schema.open();
- try {
- final HashSet<Account.Id> r = new HashSet<Account.Id>();
- for (Account a : db.accounts().byPreferredEmail(email)) {
- r.add(a.getId());
- }
- for (AccountExternalId a : db.accountExternalIds()
- .byEmailAddress(email)) {
- r.add(a.getAccountId());
- }
- return pack(r);
- } finally {
- db.close();
- }
- }
-
- @Override
- public Set<Account.Id> missing(final String key) {
- return Collections.emptySet();
- }
-
- private static Set<Account.Id> pack(final Set<Account.Id> c) {
- switch (c.size()) {
- case 0:
- return Collections.emptySet();
- case 1:
- return one(c);
- default:
- return Collections.unmodifiableSet(new HashSet<Account.Id>(c));
- }
- }
-
- private static <T> Set<T> one(final Set<T> c) {
- return Collections.singleton(c.iterator().next());
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCache.java
index 1b3626b..e35e186 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCache.java
@@ -14,15 +14,33 @@
package com.google.gerrit.server.account;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountExternalId;
+
+import java.util.Set;
/** Caches important (but small) account state to avoid database hits. */
public interface AccountCache {
- public AccountState get(Account.Id accountId);
+ // Accounts indexed by unique internal identity.
- public AccountState getByUsername(String username);
+ public ListenableFuture<AccountState> get(Account.Id accountId);
- public void evict(Account.Id accountId);
+ public ListenableFuture<Account> getAccount(Account.Id accountId);
- public void evictByUsername(String username);
+ public ListenableFuture<Void> evictAsync(Account.Id accountId);
+
+
+ // Accounts indexed by external identity (OpenID, HTTP/LDAP/SSH username).
+
+ public ListenableFuture<AccountExternalId> get(AccountExternalId.Key key);
+
+ public ListenableFuture<Void> evictAsync(AccountExternalId.Key id);
+
+
+ // Accounts indexed by email address.
+
+ public ListenableFuture<Set<Account.Id>> byEmail(String email);
+
+ public ListenableFuture<Void> evictEmailAsync(String email);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
index 52ccc66..5bd7886 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountCacheImpl.java
@@ -14,6 +14,11 @@
package com.google.gerrit.server.account;
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.AccountGroup;
@@ -23,6 +28,8 @@
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.EntryCreator;
import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.util.FutureUtil;
+import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
@@ -34,13 +41,15 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/** Caches important (but small) account state to avoid database hits. */
@Singleton
public class AccountCacheImpl implements AccountCache {
private static final String BYID_NAME = "accounts";
- private static final String BYUSER_NAME = "accounts_byname";
+ private static final String BYEXT_NAME = "accounts_byext";
+ private static final String BYEMAIL_NAME = "accounts_byemail";
public static Module module() {
return new CacheModule() {
@@ -48,11 +57,15 @@
protected void configure() {
final TypeLiteral<Cache<Account.Id, AccountState>> byIdType =
new TypeLiteral<Cache<Account.Id, AccountState>>() {};
- core(byIdType, BYID_NAME).populateWith(ByIdLoader.class);
+ cache(byIdType, BYID_NAME).populateWith(StateLoader.class);
- final TypeLiteral<Cache<String, Account.Id>> byUsernameType =
- new TypeLiteral<Cache<String, Account.Id>>() {};
- core(byUsernameType, BYUSER_NAME).populateWith(ByNameLoader.class);
+ final TypeLiteral<Cache<AccountExternalId.Key, AccountExternalId>> byKeyType =
+ new TypeLiteral<Cache<AccountExternalId.Key, AccountExternalId>>() {};
+ cache(byKeyType, BYEXT_NAME).populateWith(ExtLoader.class);
+
+ final TypeLiteral<Cache<Email, AccountIdSet>> type =
+ new TypeLiteral<Cache<Email, AccountIdSet>>() {};
+ cache(type, BYEMAIL_NAME).populateWith(EmailLoader.class);
bind(AccountCacheImpl.class);
bind(AccountCache.class).to(AccountCacheImpl.class);
@@ -61,60 +74,76 @@
}
private final Cache<Account.Id, AccountState> byId;
- private final Cache<String, Account.Id> byName;
+ private final Cache<AccountExternalId.Key, AccountExternalId> byExt;
+ private final Cache<Email, AccountIdSet> byEmail;
@Inject
AccountCacheImpl(@Named(BYID_NAME) Cache<Account.Id, AccountState> byId,
- @Named(BYUSER_NAME) Cache<String, Account.Id> byUsername) {
+ @Named(BYEXT_NAME) Cache<AccountExternalId.Key, AccountExternalId> byExt,
+ @Named(BYEMAIL_NAME) final Cache<Email, AccountIdSet> byEmail) {
this.byId = byId;
- this.byName = byUsername;
+ this.byExt = byExt;
+ this.byEmail = byEmail;
}
- public AccountState get(final Account.Id accountId) {
+ public ListenableFuture<AccountState> get(Account.Id accountId) {
return byId.get(accountId);
}
+ public ListenableFuture<Account> getAccount(Account.Id accountId) {
+ return Futures.compose(get(accountId), AccountState.GET_ACCOUNT);
+ }
+
+ public ListenableFuture<Void> evictAsync(Account.Id accountId) {
+ return byId.removeAsync(accountId);
+ }
+
@Override
- public AccountState getByUsername(String username) {
- Account.Id id = byName.get(username);
- return id != null ? byId.get(id) : null;
+ public ListenableFuture<AccountExternalId> get(AccountExternalId.Key key) {
+ return byExt.get(key);
}
- public void evict(final Account.Id accountId) {
- byId.remove(accountId);
+ @Override
+ public ListenableFuture<Void> evictAsync(AccountExternalId.Key key) {
+ return byExt.removeAsync(key);
}
- public void evictByUsername(String username) {
- byName.remove(username);
+ @Override
+ public ListenableFuture<Set<Account.Id>> byEmail(String email) {
+ if (email == null) {
+ return Futures.immediateFuture(Collections.<Account.Id> emptySet());
+ }
+ return Futures.compose(byEmail.get(new Email(email)), AccountIdSet.unpack);
}
- static class ByIdLoader extends EntryCreator<Account.Id, AccountState> {
+ @Override
+ public ListenableFuture<Void> evictEmailAsync(String email) {
+ if (email == null) {
+ return Futures.immediateFuture(null);
+ }
+ return byEmail.removeAsync(new Email(email));
+ }
+
+ static class StateLoader extends EntryCreator<Account.Id, AccountState> {
private final SchemaFactory<ReviewDb> schema;
private final Set<AccountGroup.Id> registered;
private final Set<AccountGroup.Id> anonymous;
private final GroupCache groupCache;
- private final Cache<String, Account.Id> byName;
@Inject
- ByIdLoader(SchemaFactory<ReviewDb> sf, AuthConfig auth,
- GroupCache groupCache,
- @Named(BYUSER_NAME) Cache<String, Account.Id> byUsername) {
+ StateLoader(SchemaFactory<ReviewDb> sf, AuthConfig auth,
+ GroupCache groupCache) {
this.schema = sf;
this.registered = auth.getRegisteredGroups();
this.anonymous = auth.getAnonymousGroups();
this.groupCache = groupCache;
- this.byName = byUsername;
}
@Override
public AccountState createEntry(final Account.Id key) throws Exception {
final ReviewDb db = schema.open();
try {
- final AccountState state = load(db, key);
- if (state.getUserName() != null) {
- byName.put(state.getUserName(), state.getAccount().getId());
- }
- return state;
+ return load(db, key);
} finally {
db.close();
}
@@ -129,16 +158,16 @@
return missing(who);
}
- final Collection<AccountExternalId> externalIds =
- Collections.unmodifiableCollection(db.accountExternalIds().byAccount(
- who).toList());
+ List<ListenableFuture<AccountGroup>> myGroups = Lists.newArrayList();
+ for (AccountGroupMember g : db.accountGroupMembers().byAccount(who)) {
+ myGroups.add(groupCache.get(g.getAccountGroupId()));
+ }
Set<AccountGroup.Id> internalGroups = new HashSet<AccountGroup.Id>();
- for (AccountGroupMember g : db.accountGroupMembers().byAccount(who)) {
- final AccountGroup.Id groupId = g.getAccountGroupId();
- final AccountGroup group = groupCache.get(groupId);
- if (group != null && group.getType() == AccountGroup.Type.INTERNAL) {
- internalGroups.add(groupId);
+ for (ListenableFuture<AccountGroup> f : myGroups) {
+ AccountGroup group = FutureUtil.get(f);
+ if (group.getType() == AccountGroup.Type.INTERNAL) {
+ internalGroups.add(group.getId());
}
}
@@ -149,6 +178,9 @@
internalGroups = Collections.unmodifiableSet(internalGroups);
}
+ final Collection<AccountExternalId> externalIds =
+ Collections.unmodifiableCollection(db.accountExternalIds() //
+ .byAccount(who).toList());
return new AccountState(account, internalGroups, externalIds);
}
@@ -160,26 +192,86 @@
}
}
- static class ByNameLoader extends EntryCreator<String, Account.Id> {
+ static class ExtLoader extends
+ EntryCreator<AccountExternalId.Key, AccountExternalId> {
private final SchemaFactory<ReviewDb> schema;
@Inject
- ByNameLoader(final SchemaFactory<ReviewDb> sf) {
- this.schema = sf;
+ ExtLoader(SchemaFactory<ReviewDb> schema) {
+ this.schema = schema;
}
@Override
- public Account.Id createEntry(final String username) throws Exception {
+ public AccountExternalId createEntry(AccountExternalId.Key key)
+ throws Exception {
final ReviewDb db = schema.open();
try {
- final AccountExternalId.Key key = new AccountExternalId.Key( //
- AccountExternalId.SCHEME_USERNAME, //
- username);
- final AccountExternalId id = db.accountExternalIds().get(key);
- return id != null ? id.getAccountId() : null;
+ return db.accountExternalIds().get(key);
} finally {
db.close();
}
}
}
+
+ static class Email {
+ @Column(id = 1)
+ String email;
+
+ Email() {
+ }
+
+ Email(String email) {
+ this.email = email;
+ }
+ }
+
+ static class AccountIdSet {
+ static final Function<AccountIdSet, Set<Account.Id>> unpack =
+ new Function<AccountIdSet, Set<Account.Id>>() {
+ @Override
+ public Set<Account.Id> apply(AccountIdSet from) {
+ return from.ids;
+ }
+ };
+
+ @Column(id = 1)
+ Set<Account.Id> ids;
+
+ AccountIdSet() {
+ }
+
+ AccountIdSet(Set<Account.Id> ids) {
+ this.ids = ids;
+ }
+ }
+
+ static class EmailLoader extends EntryCreator<Email, AccountIdSet> {
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ EmailLoader(final SchemaFactory<ReviewDb> schema) {
+ this.schema = schema;
+ }
+
+ @Override
+ public AccountIdSet createEntry(Email key) throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ Set<Account.Id> res = Sets.newHashSet();
+ for (AccountExternalId extId : db.accountExternalIds().byEmailAddress(
+ key.email)) {
+ res.add(extId.getAccountId());
+ }
+ return new AccountIdSet(Collections.unmodifiableSet(res));
+ } finally {
+ db.close();
+ }
+ }
+
+ @Override
+ public AccountIdSet missing(Email key) {
+ Set<Account.Id> res = Collections.emptySet();
+ return new AccountIdSet(res);
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDiffPreferencesCache.java
similarity index 65%
rename from gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDiffPreferencesCache.java
index 1eb1a4f..4fa2902 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDiffPreferencesCache.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,13 +14,12 @@
package com.google.gerrit.server.account;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
-import java.util.Set;
+public interface AccountDiffPreferencesCache {
+ public ListenableFuture<AccountDiffPreference> get(Account.Id key);
-/** Translates an email address to a set of matching accounts. */
-public interface AccountByEmailCache {
- public Set<Account.Id> get(String email);
-
- public void evict(String email);
+ public ListenableFuture<Void> evictAsync(Account.Id key);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDiffPreferencesCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDiffPreferencesCacheImpl.java
new file mode 100644
index 0000000..b54f50c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDiffPreferencesCacheImpl.java
@@ -0,0 +1,95 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountDiffPreference;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.cache.Cache;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.cache.EntryCreator;
+import com.google.gwtorm.client.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+
+@Singleton
+public class AccountDiffPreferencesCacheImpl implements
+ AccountDiffPreferencesCache {
+ private static final String BY_ACCOUNT_ID = "diff_pref";
+
+ public static Module module() {
+ return new CacheModule() {
+ @Override
+ protected void configure() {
+ final TypeLiteral<Cache<Account.Id, AccountDiffPreference>> byAccountIdType =
+ new TypeLiteral<Cache<Account.Id, AccountDiffPreference>>() {};
+ cache(byAccountIdType, BY_ACCOUNT_ID).populateWith(
+ ByAccountIdLoader.class);
+
+ bind(AccountDiffPreferencesCacheImpl.class);
+ bind(AccountDiffPreferencesCache.class).to(
+ AccountDiffPreferencesCacheImpl.class);
+ }
+ };
+ }
+
+ private final Cache<Account.Id, AccountDiffPreference> byAccountId;
+
+ @Inject
+ AccountDiffPreferencesCacheImpl(
+ @Named(BY_ACCOUNT_ID) Cache<Account.Id, AccountDiffPreference> byAccountId) {
+ this.byAccountId = byAccountId;
+ }
+
+ @Override
+ public ListenableFuture<AccountDiffPreference> get(Account.Id key) {
+ return byAccountId.get(key);
+ }
+
+ @Override
+ public ListenableFuture<Void> evictAsync(Account.Id key) {
+ return byAccountId.removeAsync(key);
+ }
+
+ static class ByAccountIdLoader extends
+ EntryCreator<Account.Id, AccountDiffPreference> {
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ ByAccountIdLoader(SchemaFactory<ReviewDb> schema) {
+ this.schema = schema;
+ }
+
+ @Override
+ public AccountDiffPreference createEntry(Account.Id id) throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ return db.accountDiffPreferences().get(id);
+ } finally {
+ db.close();
+ }
+ }
+
+ @Override
+ public AccountDiffPreference missing(Account.Id key) {
+ return AccountDiffPreference.createDefault(key);
+ }
+
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountGroupAgreementsCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountGroupAgreementsCache.java
new file mode 100644
index 0000000..f1a502e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountGroupAgreementsCache.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountGroupAgreement;
+
+import java.util.List;
+
+public interface AccountGroupAgreementsCache {
+ public ListenableFuture<List<AccountGroupAgreement>> byGroup(
+ AccountGroup.Id id);
+
+ public ListenableFuture<Void> evictAsync(AccountGroupAgreement.Key key);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountGroupAgreementsCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountGroupAgreementsCacheImpl.java
new file mode 100644
index 0000000..0f0636a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountGroupAgreementsCacheImpl.java
@@ -0,0 +1,116 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.reviewdb.AccountGroupAgreement;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.reviewdb.AccountGroupAgreement.Key;
+import com.google.gerrit.server.cache.Cache;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.cache.EntryCreator;
+import com.google.gwtorm.client.Column;
+import com.google.gwtorm.client.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+
+import java.util.List;
+
+@Singleton
+public class AccountGroupAgreementsCacheImpl implements
+ AccountGroupAgreementsCache {
+ private static final String BY_GROUP_ID = "group_agreements";
+
+ protected static class AccountGroupAgreementList {
+ @Column(id = 1)
+ protected List<AccountGroupAgreement> list;
+
+ protected AccountGroupAgreementList() {
+ }
+
+ public AccountGroupAgreementList(List<AccountGroupAgreement> list) {
+ this.list = list;
+ }
+ }
+
+ public static Module module() {
+ return new CacheModule() {
+ @Override
+ protected void configure() {
+ final TypeLiteral<Cache<AccountGroup.Id, AccountGroupAgreementList>> byGroupIdType =
+ new TypeLiteral<Cache<AccountGroup.Id, AccountGroupAgreementList>>() {};
+ cache(byGroupIdType, BY_GROUP_ID).populateWith(ByGroupIdLoader.class);
+
+ bind(AccountGroupAgreementsCacheImpl.class);
+ bind(AccountGroupAgreementsCache.class).to(
+ AccountGroupAgreementsCacheImpl.class);
+ }
+ };
+ }
+
+ private static final Function<AccountGroupAgreementList, List<AccountGroupAgreement>> unpack =
+ new Function<AccountGroupAgreementList, List<AccountGroupAgreement>>() {
+ public List<AccountGroupAgreement> apply(AccountGroupAgreementList in) {
+ return in.list;
+ }
+ };
+
+ private final Cache<AccountGroup.Id, AccountGroupAgreementList> byGroupId;
+
+ @Inject
+ AccountGroupAgreementsCacheImpl(
+ @Named(BY_GROUP_ID) Cache<AccountGroup.Id, AccountGroupAgreementList> byGroupId) {
+ this.byGroupId = byGroupId;
+ }
+
+ @Override
+ public ListenableFuture<List<AccountGroupAgreement>> byGroup(
+ AccountGroup.Id id) {
+ return Futures.compose(byGroupId.get(id), unpack);
+ }
+
+ @Override
+ public ListenableFuture<Void> evictAsync(Key key) {
+ return byGroupId.removeAsync(key.getParentKey());
+ }
+
+ static class ByGroupIdLoader extends
+ EntryCreator<AccountGroup.Id, AccountGroupAgreementList> {
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ ByGroupIdLoader(SchemaFactory<ReviewDb> schema) {
+ this.schema = schema;
+ }
+
+ @Override
+ public AccountGroupAgreementList createEntry(AccountGroup.Id id)
+ throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ return new AccountGroupAgreementList(db.accountGroupAgreements()
+ .byGroup(id).toList());
+ } finally {
+ db.close();
+ }
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountGroupCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountGroupCollection.java
new file mode 100644
index 0000000..8d9f638
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountGroupCollection.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gwtorm.client.Column;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/** Wrapper around a Collection<Account.Id> */
+public class AccountGroupCollection {
+ @Column(id = 1)
+ protected Collection<AccountGroup> groups;
+
+ protected AccountGroupCollection(){
+ }
+
+ public AccountGroupCollection(Collection<AccountGroup> groups) {
+ this.groups = Collections.unmodifiableCollection(groups);
+ }
+
+ public Collection<AccountGroup> getGroups() {
+ return groups;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfoCacheFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfoCacheFactory.java
index bb6e278..5c2e872 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfoCacheFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountInfoCacheFactory.java
@@ -14,15 +14,18 @@
package com.google.gerrit.server.account;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.common.data.AccountInfoCache;
import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.inject.Inject;
-import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Future;
/** Efficiently builds an {@link AccountInfoCache}. */
public class AccountInfoCacheFactory {
@@ -31,12 +34,12 @@
}
private final AccountCache accountCache;
- private final Map<Account.Id, Account> out;
+ private final Map<Account.Id, Future<Account>> want;
@Inject
AccountInfoCacheFactory(final AccountCache accountCache) {
this.accountCache = accountCache;
- this.out = new HashMap<Account.Id, Account>();
+ this.want = Maps.newHashMap();
}
/**
@@ -44,32 +47,34 @@
*
* @param id identity that will be needed in the future; may be null.
*/
- public void want(final Account.Id id) {
- if (id != null && !out.containsKey(id)) {
- out.put(id, accountCache.get(id).getAccount());
+ public void want(Account.Id id) {
+ if (id != null && !want.containsKey(id)) {
+ want.put(id, accountCache.getAccount(id));
}
}
/** Indicate one or more accounts will be needed later on. */
- public void want(final Iterable<Account.Id> ids) {
- for (final Account.Id id : ids) {
+ public void want(final Collection<Account.Id> ids) {
+ for (Account.Id id : ids) {
want(id);
}
}
public Account get(final Account.Id id) {
- want(id);
- return out.get(id);
+ if (id != null) {
+ want(id);
+ return FutureUtil.get(want.get(id));
+ } else {
+ return null;
+ }
}
- /**
- * Create an AccountInfoCache with the currently loaded Account entities.
- * */
+ /** Create an AccountInfoCache with the currently loaded Account entities. */
public AccountInfoCache create() {
- final List<AccountInfo> r = new ArrayList<AccountInfo>(out.size());
- for (final Account a : out.values()) {
- r.add(new AccountInfo(a));
+ List<AccountInfo> res = Lists.newArrayListWithCapacity(want.size());
+ for (Future<Account> f : want.values()) {
+ res.add(new AccountInfo(FutureUtil.get(f)));
}
- return new AccountInfoCache(r);
+ return new AccountInfoCache(res);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
index 83a5eed..3a241d3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.account;
+import com.google.common.collect.Lists;
import com.google.gerrit.common.auth.openid.OpenIdUrls;
import com.google.gerrit.common.errors.InvalidUserNameException;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
@@ -25,6 +26,8 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.util.FutureException;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
@@ -36,6 +39,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
/** Tracks authentication related details for user accounts. */
@@ -45,8 +49,7 @@
LoggerFactory.getLogger(AccountManager.class);
private final SchemaFactory<ReviewDb> schema;
- private final AccountCache byIdCache;
- private final AccountByEmailCache byEmailCache;
+ private final AccountCache accountCache;
private final AuthConfig authConfig;
private final Realm realm;
private final IdentifiedUser.GenericFactory userFactory;
@@ -55,13 +58,12 @@
@Inject
AccountManager(final SchemaFactory<ReviewDb> schema,
- final AccountCache byIdCache, final AccountByEmailCache byEmailCache,
- final AuthConfig authConfig, final Realm accountMapper,
+ final AccountCache accountCache, final AuthConfig authConfig,
+ final Realm accountMapper,
final IdentifiedUser.GenericFactory userFactory,
final ChangeUserName.Factory changeUserNameFactory) throws OrmException {
this.schema = schema;
- this.byIdCache = byIdCache;
- this.byEmailCache = byEmailCache;
+ this.accountCache = accountCache;
this.authConfig = authConfig;
this.realm = accountMapper;
this.userFactory = userFactory;
@@ -79,18 +81,13 @@
/**
* @return user identified by this external identity string, or null.
*/
- public Account.Id lookup(final String externalId) throws AccountException {
+ public Account.Id lookup(String externalId) throws AccountException {
try {
- final ReviewDb db = schema.open();
- try {
- final AccountExternalId ext =
- db.accountExternalIds().get(new AccountExternalId.Key(externalId));
- return ext != null ? ext.getAccountId() : null;
- } finally {
- db.close();
- }
- } catch (OrmException e) {
- throw new AccountException("Cannot lookup account " + externalId, e);
+ AccountExternalId.Key key = new AccountExternalId.Key(externalId);
+ AccountExternalId ext = FutureUtil.get(accountCache.get(key));
+ return ext != null ? ext.getAccountId() : null;
+ } catch (FutureException err) {
+ throw new AccountException("Cannot lookup account " + externalId, err);
}
}
@@ -107,8 +104,8 @@
try {
final ReviewDb db = schema.open();
try {
- final AccountExternalId.Key key = id(who);
- final AccountExternalId id = db.accountExternalIds().get(key);
+ AccountExternalId.Key key = id(who);
+ AccountExternalId id = FutureUtil.get(accountCache.get(key));
if (id == null) {
// New account, automatically create and return.
//
@@ -129,13 +126,16 @@
} finally {
db.close();
}
+ } catch (FutureException e) {
+ throw new AccountException("Authentication error", e);
} catch (OrmException e) {
throw new AccountException("Authentication error", e);
}
}
private void update(final ReviewDb db, final AuthRequest who,
- final AccountExternalId extId) throws OrmException {
+ AccountExternalId extId) throws OrmException {
+ final List<Future<Void>> evictions = Lists.newArrayList();
final IdentifiedUser user = userFactory.create(extId.getAccountId());
Account toUpdate = null;
@@ -151,8 +151,14 @@
toUpdate.setPreferredEmail(newEmail);
}
+ AccountExternalId.Key extKey = extId.getKey();
+ extId = db.accountExternalIds().get(extKey);
+ if (extId == null) {
+ extId = new AccountExternalId(user.getAccountId(), extKey);
+ }
extId.setEmailAddress(newEmail);
db.accountExternalIds().update(Collections.singleton(extId));
+ evictions.add(accountCache.evictAsync(extKey));
}
if (!realm.allowsEdit(Account.FieldName.FULL_NAME)
@@ -171,12 +177,13 @@
}
if (newEmail != null && !newEmail.equals(oldEmail)) {
- byEmailCache.evict(oldEmail);
- byEmailCache.evict(newEmail);
+ evictions.add(accountCache.evictEmailAsync(oldEmail));
+ evictions.add(accountCache.evictEmailAsync(newEmail));
}
if (toUpdate != null) {
- byIdCache.evict(toUpdate.getId());
+ evictions.add(accountCache.evictAsync(toUpdate.getId()));
}
+ FutureUtil.waitFor(evictions);
}
private Account load(Account toUpdate, Account.Id accountId, ReviewDb db)
@@ -196,14 +203,16 @@
private AuthResult create(final ReviewDb db, final AuthRequest who)
throws OrmException, AccountException {
+ final List<Future<Void>> evictions = Lists.newArrayList();
+
if (authConfig.isAllowGoogleAccountUpgrade()
&& who.isScheme(OpenIdUrls.URL_GOOGLE + "?")
&& who.getEmailAddress() != null) {
final List<AccountExternalId> openId = new ArrayList<AccountExternalId>();
final List<AccountExternalId> v1 = new ArrayList<AccountExternalId>();
- for (final AccountExternalId extId : db.accountExternalIds()
- .byEmailAddress(who.getEmailAddress())) {
+ for (AccountExternalId extId : db.accountExternalIds().byEmailAddress(
+ who.getEmailAddress())) {
if (extId.isScheme(OpenIdUrls.URL_GOOGLE + "?")) {
openId.add(extId);
} else if (extId.isScheme(AccountExternalId.LEGACY_GAE)) {
@@ -236,9 +245,13 @@
final AccountExternalId oldId = openId.get(0);
db.accountExternalIds().upsert(Collections.singleton(newId));
db.accountExternalIds().delete(Collections.singleton(oldId));
+ evictions.add(accountCache.evictAsync(newId.getKey()));
+ evictions.add(accountCache.evictAsync(oldId.getKey()));
} else {
db.accountExternalIds().insert(Collections.singleton(newId));
+ evictions.add(accountCache.evictAsync(newId.getKey()));
}
+ FutureUtil.waitFor(evictions);
return new AuthResult(accountId, newId.getKey(), false);
} else if (v1.size() == 1) {
@@ -252,6 +265,9 @@
db.accountExternalIds().upsert(Collections.singleton(newId));
db.accountExternalIds().delete(Collections.singleton(oldId));
+ evictions.add(accountCache.evictAsync(newId.getKey()));
+ evictions.add(accountCache.evictAsync(oldId.getKey()));
+ FutureUtil.waitFor(evictions);
return new AuthResult(newId.getAccountId(), newId.getKey(), false);
} else if (v1.size() > 1) {
@@ -269,6 +285,7 @@
db.accounts().insert(Collections.singleton(account));
db.accountExternalIds().insert(Collections.singleton(extId));
+ evictions.add(accountCache.evictAsync(extId.getKey()));
if (firstAccount.get() && firstAccount.compareAndSet(true, false)) {
// This is the first user account on our site. Assume this user
@@ -300,7 +317,9 @@
}
}
- byEmailCache.evict(account.getPreferredEmail());
+ evictions.add(accountCache.evictEmailAsync(account.getPreferredEmail()));
+ FutureUtil.waitFor(evictions);
+
realm.onCreateAccount(who, account);
return new AuthResult(newId, extId.getKey(), true);
}
@@ -325,8 +344,9 @@
try {
final ReviewDb db = schema.open();
try {
- final AccountExternalId.Key key = id(who);
- AccountExternalId extId = db.accountExternalIds().get(key);
+ List<Future<Void>> evictions = Lists.newArrayList();
+ AccountExternalId.Key key = id(who);
+ AccountExternalId extId = FutureUtil.get(accountCache.get(key));
if (extId != null) {
if (!extId.getAccountId().equals(to)) {
throw new AccountException("Identity in use by another account");
@@ -337,6 +357,7 @@
extId = createId(to, who);
extId.setEmailAddress(who.getEmailAddress());
db.accountExternalIds().insert(Collections.singleton(extId));
+ evictions.add(accountCache.evictAsync(extId.getKey()));
if (who.getEmailAddress() != null) {
final Account a = db.accounts().get(to);
@@ -347,16 +368,19 @@
}
if (who.getEmailAddress() != null) {
- byEmailCache.evict(who.getEmailAddress());
- byIdCache.evict(to);
+ evictions.add(accountCache.evictEmailAsync(who.getEmailAddress()));
+ evictions.add(accountCache.evictAsync(to));
}
}
+ FutureUtil.waitFor(evictions);
return new AuthResult(to, key, false);
} finally {
db.close();
}
+ } catch (FutureException e) {
+ throw new AccountException("Cannot link identity", e);
} catch (OrmException e) {
throw new AccountException("Cannot link identity", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountProjectWatchCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountProjectWatchCache.java
new file mode 100644
index 0000000..3e8a755
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountProjectWatchCache.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
+import com.google.gerrit.reviewdb.Project;
+
+import java.util.List;
+
+public interface AccountProjectWatchCache {
+ public ListenableFuture<List<AccountProjectWatch>> byAccount(Account.Id id);
+
+ public ListenableFuture<List<AccountProjectWatch>> byProject(Project.NameKey name);
+
+ public ListenableFuture<Void> evictAsync(AccountProjectWatch.Key key);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountProjectWatchCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountProjectWatchCacheImpl.java
new file mode 100644
index 0000000..c683031
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountProjectWatchCacheImpl.java
@@ -0,0 +1,154 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.reviewdb.AccountProjectWatch;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.cache.Cache;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.cache.EntryCreator;
+import com.google.gerrit.server.util.CompoundFuture;
+import com.google.gwtorm.client.Column;
+import com.google.gwtorm.client.SchemaFactory;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+
+import java.util.List;
+
+@Singleton
+public class AccountProjectWatchCacheImpl implements AccountProjectWatchCache {
+ private static final String BY_ACCOUNT_ID = "apw_account";
+ private static final String BY_PROJECT_NAME = "apw_project";
+
+ protected static class AccountProjectWatchList {
+ @Column(id = 1)
+ protected List<AccountProjectWatch> list;
+
+ protected AccountProjectWatchList() {
+ }
+
+ public AccountProjectWatchList(List<AccountProjectWatch> list) {
+ this.list = list;
+ }
+ }
+
+ public static Module module() {
+ return new CacheModule() {
+ @Override
+ protected void configure() {
+ final TypeLiteral<Cache<Account.Id, AccountProjectWatchList>> byAccountIdType =
+ new TypeLiteral<Cache<Account.Id, AccountProjectWatchList>>() {};
+ cache(byAccountIdType, BY_ACCOUNT_ID).populateWith(
+ ByAccountIdLoader.class);
+
+ final TypeLiteral<Cache<Project.NameKey, AccountProjectWatchList>> byProjectNameType =
+ new TypeLiteral<Cache<Project.NameKey, AccountProjectWatchList>>() {};
+ cache(byProjectNameType, BY_PROJECT_NAME).populateWith(
+ ByProjectNameLoader.class);
+
+ bind(AccountProjectWatchCacheImpl.class);
+ bind(AccountProjectWatchCache.class).to(
+ AccountProjectWatchCacheImpl.class);
+ }
+ };
+ }
+
+ private static final Function<AccountProjectWatchList, List<AccountProjectWatch>> unpack =
+ new Function<AccountProjectWatchList, List<AccountProjectWatch>>() {
+ @Override
+ public List<AccountProjectWatch> apply(AccountProjectWatchList in) {
+ return in.list;
+ }
+ };
+
+ private final Cache<Account.Id, AccountProjectWatchList> byAccountId;
+ private final Cache<Project.NameKey, AccountProjectWatchList> byProjectName;
+
+ @Inject
+ AccountProjectWatchCacheImpl(
+ @Named(BY_ACCOUNT_ID) Cache<Account.Id, AccountProjectWatchList> byAccountId,
+ @Named(BY_PROJECT_NAME) Cache<Project.NameKey, AccountProjectWatchList> byProjectName) {
+ this.byAccountId = byAccountId;
+ this.byProjectName = byProjectName;
+ }
+
+ @Override
+ public ListenableFuture<List<AccountProjectWatch>> byAccount(Account.Id id) {
+ return Futures.compose(byAccountId.get(id), unpack);
+ }
+
+ @Override
+ public ListenableFuture<List<AccountProjectWatch>> byProject(
+ Project.NameKey name) {
+ return Futures.compose(byProjectName.get(name), unpack);
+ }
+
+ @Override
+ public ListenableFuture<Void> evictAsync(AccountProjectWatch.Key key) {
+ return CompoundFuture.wrap(byAccountId.removeAsync(key.getParentKey()),
+ byProjectName.removeAsync(key.getProjectName()));
+ }
+
+ static class ByAccountIdLoader extends
+ EntryCreator<Account.Id, AccountProjectWatchList> {
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ ByAccountIdLoader(SchemaFactory<ReviewDb> schema) {
+ this.schema = schema;
+ }
+
+ @Override
+ public AccountProjectWatchList createEntry(Account.Id id) throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ return new AccountProjectWatchList(db.accountProjectWatches()
+ .byAccount(id).toList());
+ } finally {
+ db.close();
+ }
+ }
+ }
+
+ static class ByProjectNameLoader extends
+ EntryCreator<Project.NameKey, AccountProjectWatchList> {
+ private final SchemaFactory<ReviewDb> schema;
+
+ @Inject
+ ByProjectNameLoader(SchemaFactory<ReviewDb> schema) {
+ this.schema = schema;
+ }
+
+ @Override
+ public AccountProjectWatchList createEntry(Project.NameKey name)
+ throws Exception {
+ final ReviewDb db = schema.open();
+ try {
+ return new AccountProjectWatchList(db.accountProjectWatches()
+ .byProject(name).toList());
+ } finally {
+ db.close();
+ }
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
index 3dce94e..d5dea63 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
@@ -17,6 +17,7 @@
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -30,16 +31,14 @@
public class AccountResolver {
private final Realm realm;
- private final AccountByEmailCache byEmail;
- private final AccountCache byId;
+ private final AccountCache accountCache;
private final Provider<ReviewDb> schema;
@Inject
- AccountResolver(final Realm realm, final AccountByEmailCache byEmail,
- final AccountCache byId, final Provider<ReviewDb> schema) {
+ AccountResolver(final Realm realm, final AccountCache accountCache,
+ final Provider<ReviewDb> schema) {
this.realm = realm;
- this.byEmail = byEmail;
- this.byId = byId;
+ this.accountCache = accountCache;
this.schema = schema;
}
@@ -55,7 +54,10 @@
*/
public Account find(final String nameOrEmail) throws OrmException {
Set<Account.Id> r = findAll(nameOrEmail);
- return r.size() == 1 ? byId.get(r.iterator().next()).getAccount() : null;
+ if (r.size() == 1) {
+ return FutureUtil.get(accountCache.getAccount(r.iterator().next()));
+ }
+ return null;
}
/**
@@ -65,10 +67,11 @@
* "Full Name <email@example>", just the email address
* ("email@example"), a full name ("Full Name"), an account id
* ("18419") or an user name ("username").
- * @return the accounts that match, empty collection if none. Never null.
+ * @return the accounts that match, empty collection if none. Never null.
*/
public Set<Account.Id> findAll(String nameOrEmail) throws OrmException {
- Matcher m = Pattern.compile("^.* \\(([1-9][0-9]*)\\)$").matcher(nameOrEmail);
+ Matcher m =
+ Pattern.compile("^.* \\(([1-9][0-9]*)\\)$").matcher(nameOrEmail);
if (m.matches()) {
return Collections.singleton(Account.Id.parse(m.group(1)));
}
@@ -78,9 +81,10 @@
}
if (nameOrEmail.matches(Account.USER_NAME_PATTERN)) {
- AccountState who = byId.getByUsername(nameOrEmail);
+ AccountExternalId.Key key = AccountExternalId.forUsername(nameOrEmail);
+ AccountExternalId who = FutureUtil.get(accountCache.get(key));
if (who != null) {
- return Collections.singleton(who.getAccount().getId());
+ return Collections.singleton(who.getAccountId());
}
}
@@ -99,7 +103,10 @@
public Account findByNameOrEmail(final String nameOrEmail)
throws OrmException {
Set<Account.Id> r = findAllByNameOrEmail(nameOrEmail);
- return r.size() == 1 ? byId.get(r.iterator().next()).getAccount() : null;
+ if (r.size() == 1) {
+ return FutureUtil.get(accountCache.getAccount(r.iterator().next()));
+ }
+ return null;
}
/**
@@ -115,11 +122,12 @@
final int lt = nameOrEmail.indexOf('<');
final int gt = nameOrEmail.indexOf('>');
if (lt >= 0 && gt > lt && nameOrEmail.contains("@")) {
- return byEmail.get(nameOrEmail.substring(lt + 1, gt));
+ String email = nameOrEmail.substring(lt + 1, gt);
+ return FutureUtil.get(accountCache.byEmail(email));
}
if (nameOrEmail.contains("@")) {
- return byEmail.get(nameOrEmail);
+ return FutureUtil.get(accountCache.byEmail(nameOrEmail));
}
final Account.Id id = realm.lookup(nameOrEmail);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
index 9393227..667d220 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
@@ -16,18 +16,39 @@
import static com.google.gerrit.reviewdb.AccountExternalId.SCHEME_USERNAME;
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gwtorm.client.Column;
import java.util.Collection;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
public class AccountState {
- private final Account account;
- private final Set<AccountGroup.Id> internalGroups;
- private final Collection<AccountExternalId> externalIds;
+ /** Convert from an AccountState to an Account. */
+ public static final Function<AccountState, Account> GET_ACCOUNT =
+ new Function<AccountState, Account>() {
+ @Override
+ public Account apply(AccountState in) {
+ return in.getAccount();
+ }
+ };
+
+ @Column(id = 1)
+ protected Account account;
+
+ @Column(id = 2)
+ protected Set<AccountGroup.Id> internalGroups;
+
+ @Column(id = 3)
+ protected Collection<AccountExternalId> externalIds;
+
+ protected AccountState() {
+ }
public AccountState(final Account account,
final Set<AccountGroup.Id> actualGroups,
@@ -53,17 +74,6 @@
return account.getUserName();
}
- /** @return the password matching the requested username; or null. */
- public String getPassword(String username) {
- for (AccountExternalId id : getExternalIds()) {
- if (id.isScheme(AccountExternalId.SCHEME_USERNAME)
- && username.equals(id.getSchemeRest())) {
- return id.getPassword();
- }
- }
- return null;
- }
-
/**
* All email addresses registered to this account.
* <p>
@@ -88,6 +98,18 @@
return externalIds;
}
+ /** The external identities that match a particular email address. */
+ public Collection<AccountExternalId> getExternalIds(String emailAddress) {
+ List<AccountExternalId> r = Lists.newArrayListWithCapacity(externalIds.size());
+ for (AccountExternalId extId : externalIds) {
+ String accEmail = extId.getEmailAddress();
+ if (accEmail != null && accEmail.equals(emailAddress)) {
+ r.add(extId);
+ }
+ }
+ return r;
+ }
+
/** The set of groups maintained directly within the Gerrit database. */
public Set<AccountGroup.Id> getInternalGroups() {
return internalGroups;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java
index e875a19..ecdb4b9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java
@@ -16,6 +16,7 @@
import static com.google.gerrit.reviewdb.AccountExternalId.SCHEME_USERNAME;
+import com.google.common.collect.Lists;
import com.google.gerrit.common.errors.InvalidUserNameException;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.reviewdb.Account;
@@ -23,6 +24,7 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.ssh.SshKeyCache;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
@@ -30,10 +32,10 @@
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
@@ -72,7 +74,7 @@
private final ReviewDb db;
private final IdentifiedUser user;
- private final String newUsername;
+ private final String newName;
@Inject
ChangeUserName(final AccountCache accountCache,
@@ -85,70 +87,41 @@
this.db = db;
this.user = user;
- this.newUsername = newUsername;
+ this.newName = newUsername;
}
public VoidResult call() throws OrmException, NameAlreadyUsedException,
InvalidUserNameException {
- final Collection<AccountExternalId> old = old();
- if (!old.isEmpty()) {
+ if (hasUsername()) {
throw new IllegalStateException("Username cannot be changed.");
}
-
- if (newUsername != null && !newUsername.isEmpty()) {
- if (!USER_NAME_PATTERN.matcher(newUsername).matches()) {
- throw new InvalidUserNameException();
- }
-
- final AccountExternalId.Key key =
- new AccountExternalId.Key(SCHEME_USERNAME, newUsername);
- try {
- final AccountExternalId id =
- new AccountExternalId(user.getAccountId(), key);
-
- for (AccountExternalId i : old) {
- if (i.getPassword() != null) {
- id.setPassword(i.getPassword());
- }
- }
-
- db.accountExternalIds().insert(Collections.singleton(id));
- } catch (OrmDuplicateKeyException dupeErr) {
- // If we are using this identity, don't report the exception.
- //
- AccountExternalId other = db.accountExternalIds().get(key);
- if (other != null && other.getAccountId().equals(user.getAccountId())) {
- return VoidResult.INSTANCE;
- }
-
- // Otherwise, someone else has this identity.
- //
- throw new NameAlreadyUsedException();
- }
+ if (newName == null || !USER_NAME_PATTERN.matcher(newName).matches()) {
+ throw new InvalidUserNameException();
}
- // If we have any older user names, remove them.
- //
- db.accountExternalIds().delete(old);
- for (AccountExternalId i : old) {
- sshKeyCache.evict(i.getSchemeRest());
- accountCache.evictByUsername(i.getSchemeRest());
+ AccountExternalId.Key key = AccountExternalId.forUsername(newName);
+ List<Future<Void>> evictions = Lists.newArrayList();
+ try {
+ AccountExternalId id = new AccountExternalId(user.getAccountId(), key);
+ db.accountExternalIds().insert(Collections.singleton(id));
+ evictions.add(accountCache.evictAsync(key));
+ } catch (OrmDuplicateKeyException dupeErr) {
+ throw new NameAlreadyUsedException();
}
- accountCache.evict(user.getAccountId());
- accountCache.evictByUsername(newUsername);
- sshKeyCache.evict(newUsername);
+ evictions.add(accountCache.evictAsync(user.getAccountId()));
+ evictions.add(accountCache.evictAsync(key));
+ evictions.add(sshKeyCache.evictAsync(newName));
+ FutureUtil.waitFor(evictions);
return VoidResult.INSTANCE;
}
- private Collection<AccountExternalId> old() throws OrmException {
- final Collection<AccountExternalId> r = new ArrayList<AccountExternalId>(1);
- for (AccountExternalId i : db.accountExternalIds().byAccount(
- user.getAccountId())) {
+ private boolean hasUsername() {
+ for (AccountExternalId i : user.getAccountState().getExternalIds()) {
if (i.isScheme(SCHEME_USERNAME)) {
- r.add(i);
+ return true;
}
}
- return r;
+ return false;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/ClearPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/ClearPassword.java
index 1fc87fe..ae12851 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/ClearPassword.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/ClearPassword.java
@@ -14,16 +14,20 @@
package com.google.gerrit.server.account;
+import com.google.common.collect.Lists;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
/** Operation to clear a password for an account. */
public class ClearPassword implements Callable<AccountExternalId> {
@@ -57,7 +61,11 @@
id.setPassword(null);
db.accountExternalIds().update(Collections.singleton(id));
- accountCache.evict(user.getAccountId());
+
+ List<Future<Void>> evictions = Lists.newArrayList();
+ evictions.add(accountCache.evictAsync(id.getKey()));
+ evictions.add(accountCache.evictAsync(user.getAccountId()));
+ FutureUtil.waitFor(evictions);
return id;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
index a836f54..5bd0fdd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
@@ -16,6 +16,7 @@
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.inject.Inject;
import java.util.Collections;
@@ -23,13 +24,13 @@
public final class DefaultRealm implements Realm {
private final EmailExpander emailExpander;
- private final AccountByEmailCache byEmail;
+ private final AccountCache accountCache;
@Inject
DefaultRealm(final EmailExpander emailExpander,
- final AccountByEmailCache byEmail) {
+ final AccountCache accountCache) {
this.emailExpander = emailExpander;
- this.byEmail = byEmail;
+ this.accountCache = accountCache;
}
@Override
@@ -58,7 +59,8 @@
@Override
public Account.Id lookup(final String accountName) {
if (emailExpander.canExpand(accountName)) {
- final Set<Account.Id> c = byEmail.get(emailExpander.expand(accountName));
+ Set<Account.Id> c = FutureUtil.getOrEmptySet( //
+ accountCache.byEmail(emailExpander.expand(accountName)));
if (1 == c.size()) {
return c.iterator().next();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GeneratePassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GeneratePassword.java
index 1b30503..6964929 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GeneratePassword.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GeneratePassword.java
@@ -14,10 +14,13 @@
package com.google.gerrit.server.account;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -27,6 +30,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Collections;
+import java.util.List;
import java.util.concurrent.Callable;
/** Operation to generate a password for an account. */
@@ -50,29 +54,32 @@
private final ReviewDb db;
private final IdentifiedUser user;
- private final AccountExternalId.Key forUser;
+ private final AccountExternalId.Key idKey;
@Inject
GeneratePassword(final AccountCache accountCache, final ReviewDb db,
final IdentifiedUser user,
- @Assisted AccountExternalId.Key forUser) {
+ @Assisted AccountExternalId.Key idKey) {
this.accountCache = accountCache;
this.db = db;
this.user = user;
- this.forUser = forUser;
+ this.idKey = idKey;
}
public AccountExternalId call() throws OrmException, NoSuchEntityException {
- AccountExternalId id = db.accountExternalIds().get(forUser);
+ AccountExternalId id = db.accountExternalIds().get(idKey);
if (id == null || !user.getAccountId().equals(id.getAccountId())) {
throw new NoSuchEntityException();
}
+ List<ListenableFuture<Void>> evictions = Lists.newArrayList();
id.setPassword(generate());
db.accountExternalIds().update(Collections.singleton(id));
- accountCache.evict(user.getAccountId());
+ evictions.add(accountCache.evictAsync(user.getAccountId()));
+ evictions.add(accountCache.evictAsync(idKey));
+ FutureUtil.waitFor(evictions);
return id;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java
index 978d9c2..daf0312 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCache.java
@@ -14,19 +14,20 @@
package com.google.gerrit.server.account;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.reviewdb.AccountGroup;
-import java.util.Collection;
-
/** Tracks group objects in memory for efficient access. */
public interface GroupCache {
- public AccountGroup get(AccountGroup.Id groupId);
+ public ListenableFuture<AccountGroup> get(AccountGroup.Id groupId);
- public AccountGroup get(AccountGroup.NameKey name);
+ public ListenableFuture<AccountGroup> get(AccountGroup.NameKey name);
- public Collection<AccountGroup> get(AccountGroup.ExternalNameKey externalName);
+ public ListenableFuture<AccountGroupCollection> get(
+ AccountGroup.ExternalNameKey externalName);
- public void evict(AccountGroup group);
+ public ListenableFuture<Void> evictAsync(AccountGroup group);
- public void evictAfterRename(AccountGroup.NameKey oldName);
+ public ListenableFuture<Void> evictAfterRenameAsync(
+ AccountGroup.NameKey oldName);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
index d948aef..b3a858a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupCacheImpl.java
@@ -14,13 +14,16 @@
package com.google.gerrit.server.account;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.reviewdb.AccountGroup.ExternalNameKey;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.EntryCreator;
import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.util.CompoundFuture;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Module;
@@ -28,7 +31,7 @@
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
-import java.util.Collection;
+import java.util.Collections;
/** Tracks group objects in memory for efficient access. */
@Singleton
@@ -43,15 +46,15 @@
protected void configure() {
final TypeLiteral<Cache<AccountGroup.Id, AccountGroup>> byId =
new TypeLiteral<Cache<AccountGroup.Id, AccountGroup>>() {};
- core(byId, BYID_NAME).populateWith(ByIdLoader.class);
+ cache(byId, BYID_NAME).populateWith(ByIdLoader.class);
final TypeLiteral<Cache<AccountGroup.NameKey, AccountGroup>> byName =
new TypeLiteral<Cache<AccountGroup.NameKey, AccountGroup>>() {};
- core(byName, BYNAME_NAME).populateWith(ByNameLoader.class);
+ cache(byName, BYNAME_NAME).populateWith(ByNameLoader.class);
- final TypeLiteral<Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>>> byExternalName =
- new TypeLiteral<Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>>>() {};
- core(byExternalName, BYEXT_NAME) //
+ final TypeLiteral<Cache<AccountGroup.ExternalNameKey, AccountGroupCollection>> byExternalName =
+ new TypeLiteral<Cache<AccountGroup.ExternalNameKey, AccountGroupCollection>>() {};
+ cache(byExternalName, BYEXT_NAME) //
.populateWith(ByExternalNameLoader.class);
bind(GroupCacheImpl.class);
@@ -62,38 +65,38 @@
private final Cache<AccountGroup.Id, AccountGroup> byId;
private final Cache<AccountGroup.NameKey, AccountGroup> byName;
- private final Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>> byExternalName;
+ private final Cache<AccountGroup.ExternalNameKey, AccountGroupCollection> byExternalName;
@Inject
GroupCacheImpl(
@Named(BYID_NAME) Cache<AccountGroup.Id, AccountGroup> byId,
@Named(BYNAME_NAME) Cache<AccountGroup.NameKey, AccountGroup> byName,
- @Named(BYEXT_NAME) Cache<AccountGroup.ExternalNameKey, Collection<AccountGroup>> byExternalName) {
+ @Named(BYEXT_NAME) Cache<AccountGroup.ExternalNameKey, AccountGroupCollection> byExternalName) {
this.byId = byId;
this.byName = byName;
this.byExternalName = byExternalName;
}
- public AccountGroup get(final AccountGroup.Id groupId) {
+ public ListenableFuture<AccountGroup> get(final AccountGroup.Id groupId) {
return byId.get(groupId);
}
- public void evict(final AccountGroup group) {
- byId.remove(group.getId());
- byName.remove(group.getNameKey());
- byExternalName.remove(group.getExternalNameKey());
+ public ListenableFuture<Void> evictAsync(AccountGroup group) {
+ return CompoundFuture.wrap(byId.removeAsync(group.getId()), //
+ byName.removeAsync(group.getNameKey()),//
+ byExternalName.removeAsync(group.getExternalNameKey()));
}
- public void evictAfterRename(final AccountGroup.NameKey oldName) {
- byName.remove(oldName);
+ public ListenableFuture<Void> evictAfterRenameAsync(AccountGroup.NameKey name) {
+ return byName.removeAsync(name);
}
- public AccountGroup get(final AccountGroup.NameKey name) {
+ public ListenableFuture<AccountGroup> get(AccountGroup.NameKey name) {
return byName.get(name);
}
- public Collection<AccountGroup> get(
- final AccountGroup.ExternalNameKey externalName) {
+ public ListenableFuture<AccountGroupCollection> get(
+ AccountGroup.ExternalNameKey externalName) {
return byExternalName.get(externalName);
}
@@ -161,7 +164,7 @@
}
static class ByExternalNameLoader extends
- EntryCreator<AccountGroup.ExternalNameKey, Collection<AccountGroup>> {
+ EntryCreator<AccountGroup.ExternalNameKey, AccountGroupCollection> {
private final SchemaFactory<ReviewDb> schema;
@Inject
@@ -170,14 +173,20 @@
}
@Override
- public Collection<AccountGroup> createEntry(
+ public AccountGroupCollection createEntry(
final AccountGroup.ExternalNameKey key) throws Exception {
final ReviewDb db = schema.open();
try {
- return db.accountGroups().byExternalName(key).toList();
+ return new AccountGroupCollection(db.accountGroups()
+ .byExternalName(key).toList());
} finally {
db.close();
}
}
+
+ @Override
+ public AccountGroupCollection missing(ExternalNameKey key) {
+ return new AccountGroupCollection(Collections.<AccountGroup> emptyList());
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
index 40360b9..4b1ad2b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java
@@ -17,6 +17,7 @@
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -34,7 +35,7 @@
public GroupControl controlFor(final AccountGroup.Id groupId)
throws NoSuchGroupException {
- final AccountGroup group = groupCache.get(groupId);
+ AccountGroup group = FutureUtil.getOrNull(groupCache.get(groupId));
if (group == null) {
throw new NoSuchGroupException(groupId);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
index 6f6a4d4..b95e39d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
@@ -19,6 +19,7 @@
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gerrit.util.ssl.BlindSSLSocketFactory;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -41,7 +42,8 @@
import javax.naming.directory.InitialDirContext;
import javax.net.ssl.SSLSocketFactory;
-@Singleton class Helper {
+@Singleton
+class Helper {
private final GroupCache groupCache;
private final Config config;
private final String server;
@@ -132,8 +134,8 @@
}
Set<AccountGroup.Id> queryForGroups(final DirContext ctx,
- final String username, LdapQuery.Result account)
- throws NamingException, AccountException {
+ final String username, LdapQuery.Result account) throws NamingException,
+ AccountException {
final LdapSchema schema = getSchema(ctx);
final Set<String> groupDNs = new HashSet<String>();
@@ -175,8 +177,8 @@
final Set<AccountGroup.Id> actual = new HashSet<AccountGroup.Id>();
for (String dn : groupDNs) {
- for (AccountGroup group : groupCache
- .get(new AccountGroup.ExternalNameKey(dn))) {
+ for (AccountGroup group : FutureUtil.get(
+ groupCache.get(new AccountGroup.ExternalNameKey(dn))).getGroups()) {
if (group.getType() == AccountGroup.Type.LDAP) {
actual.add(group.getId());
}
@@ -238,9 +240,11 @@
groupBases = LdapRealm.optionalList(config, "groupBase");
groupScope = LdapRealm.scope(config, "groupScope");
- groupPattern = LdapRealm.paramString(config, "groupPattern", type.groupPattern());
+ groupPattern =
+ LdapRealm.paramString(config, "groupPattern", type.groupPattern());
final String groupMemberPattern =
- LdapRealm.optdef(config, "groupMemberPattern", type.groupMemberPattern());
+ LdapRealm.optdef(config, "groupMemberPattern", type
+ .groupMemberPattern());
for (String groupBase : groupBases) {
if (groupMemberPattern != null) {
@@ -266,7 +270,8 @@
// Account query
//
accountFullName =
- LdapRealm.paramString(config, "accountFullName", type.accountFullName());
+ LdapRealm.paramString(config, "accountFullName", type
+ .accountFullName());
if (accountFullName != null) {
accountAtts.addAll(accountFullName.getParameterNames());
}
@@ -277,12 +282,14 @@
accountAtts.addAll(accountEmailAddress.getParameterNames());
}
accountSshUserName =
- LdapRealm.paramString(config, "accountSshUserName", type.accountSshUserName());
+ LdapRealm.paramString(config, "accountSshUserName", type
+ .accountSshUserName());
if (accountSshUserName != null) {
accountAtts.addAll(accountSshUserName.getParameterNames());
}
accountMemberField =
- LdapRealm.optdef(config, "accountMemberField", type.accountMemberField());
+ LdapRealm.optdef(config, "accountMemberField", type
+ .accountMemberField());
if (accountMemberField != null) {
accountAtts.add(accountMemberField);
}
@@ -313,4 +320,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapModule.java
index 810df28..d16b635 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapModule.java
@@ -34,12 +34,12 @@
protected void configure() {
final TypeLiteral<Cache<String, Set<AccountGroup.Id>>> groups =
new TypeLiteral<Cache<String, Set<AccountGroup.Id>>>() {};
- core(groups, GROUP_CACHE).maxAge(1, HOURS) //
+ cache(groups, GROUP_CACHE).maxAge(1, HOURS) //
.populateWith(LdapRealm.MemberLoader.class);
final TypeLiteral<Cache<String, Account.Id>> usernames =
new TypeLiteral<Cache<String, Account.Id>>() {};
- core(usernames, USERNAME_CACHE) //
+ cache(usernames, USERNAME_CACHE) //
.populateWith(LdapRealm.UserLoader.class);
bind(Realm.class).to(LdapRealm.class).in(Scopes.SINGLETON);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
index de33b44..80bfb60 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
@@ -21,7 +21,7 @@
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AuthType;
-import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.AuthRequest;
@@ -33,8 +33,7 @@
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.client.SchemaFactory;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
@@ -110,14 +109,12 @@
return v;
}
- static List<String> optionalList(final Config config,
- final String name) {
+ static List<String> optionalList(final Config config, final String name) {
String s[] = config.getStringList("ldap", null, name);
return Arrays.asList(s);
}
- static List<String> requiredList(final Config config,
- final String name) {
+ static List<String> requiredList(final Config config, final String name) {
List<String> vlist = optionalList(config, name);
if (vlist.isEmpty()) {
@@ -220,7 +217,8 @@
// in the middle of authenticating the user, its likely we will
// need to know what access rights they have soon.
//
- membershipCache.put(username, helper.queryForGroups(ctx, username, m));
+ FutureUtil.waitFor(membershipCache.putAsync(username, //
+ helper.queryForGroups(ctx, username, m)));
return who;
} finally {
try {
@@ -237,13 +235,14 @@
@Override
public void onCreateAccount(final AuthRequest who, final Account account) {
- usernameCache.put(who.getLocalUser(), account.getId());
+ String name = who.getLocalUser();
+ FutureUtil.waitFor(usernameCache.putAsync(name, account.getId()));
}
@Override
public Set<AccountGroup.Id> groups(final AccountState who) {
final HashSet<AccountGroup.Id> r = new HashSet<AccountGroup.Id>();
- r.addAll(membershipCache.get(findId(who.getExternalIds())));
+ r.addAll(FutureUtil.get(membershipCache.get(findId(who.getExternalIds()))));
r.addAll(who.getInternalGroups());
return r;
}
@@ -260,7 +259,7 @@
@Override
public Account.Id lookup(final String accountName) {
- return usernameCache.get(accountName);
+ return FutureUtil.get(usernameCache.get(accountName));
}
@Override
@@ -298,29 +297,18 @@
}
static class UserLoader extends EntryCreator<String, Account.Id> {
- private final SchemaFactory<ReviewDb> schema;
+ private final AccountCache accountCache;
@Inject
- UserLoader(SchemaFactory<ReviewDb> schema) {
- this.schema = schema;
+ UserLoader(AccountCache accountCache) {
+ this.accountCache = accountCache;
}
@Override
public Account.Id createEntry(final String username) throws Exception {
- try {
- final ReviewDb db = schema.open();
- try {
- final AccountExternalId extId =
- db.accountExternalIds().get(
- new AccountExternalId.Key(SCHEME_GERRIT, username));
- return extId != null ? extId.getAccountId() : null;
- } finally {
- db.close();
- }
- } catch (OrmException e) {
- log.warn("Cannot query for username in database", e);
- return null;
- }
+ AccountExternalId extId = FutureUtil.get( //
+ accountCache.get(new AccountExternalId.Key(SCHEME_GERRIT, username)));
+ return extId != null ? extId.getAccountId() : null;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/Cache.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/Cache.java
index 7159501..f91d3c8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/Cache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/Cache.java
@@ -14,30 +14,36 @@
package com.google.gerrit.server.cache;
+import com.google.common.util.concurrent.ListenableFuture;
+
import java.util.concurrent.TimeUnit;
-
/**
- * A fast in-memory and/or on-disk based cache.
+ * An in-memory and/or on-disk based cache.
+ *
+ * The cache may need to perform operations over the network, in which case the
+ * returned Future can be used to wait for operation completion. Opening
+ * multiple concurrent Futures on such a cache may permit the cache to batch the
+ * operations together (but that is implementation specific).
*
* @type <K> type of key used to lookup entries in the cache.
* @type <V> type of value stored within each cache entry.
*/
public interface Cache<K, V> {
/** Get the element from the cache, or null if not stored in the cache. */
- public V get(K key);
+ public ListenableFuture<V> get(K key);
/** Put one element into the cache, replacing any existing value. */
- public void put(K key, V value);
+ public ListenableFuture<Void> putAsync(K key, V value);
/** Remove any existing value from the cache, no-op if not present. */
- public void remove(K key);
+ public ListenableFuture<Void> removeAsync(K key);
/** Remove all cached items. */
- public void removeAll();
+ public ListenableFuture<Void> removeAllAsync();
/**
- * Get the time an element will survive in the cache.
+ * Get the configured time an element will survive in the cache.
*
* @param unit desired units of the return value.
* @return time an item can live before being purged.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheModule.java
index 7fb3b3b..c185f7c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheModule.java
@@ -22,8 +22,6 @@
import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.name.Names;
-import java.io.Serializable;
-
/**
* Miniature DSL to support binding {@link Cache} instances in Guice.
*/
@@ -38,9 +36,9 @@
* @return binding to describe the cache. Caller must set at least the name on
* the returned binding.
*/
- protected <K, V> UnnamedCacheBinding<K, V> core(
+ protected <K, V> UnnamedCacheBinding<K, V> cache(
final TypeLiteral<Cache<K, V>> type) {
- return core(Key.get(type));
+ return cache(Key.get(type), type);
}
/**
@@ -53,52 +51,15 @@
* and with {@code @Named} annotations.
* @return binding to describe the cache.
*/
- protected <K, V> NamedCacheBinding<K, V> core(
+ protected <K, V> NamedCacheBinding<K, V> cache(
final TypeLiteral<Cache<K, V>> type, final String name) {
- return core(Key.get(type, Names.named(name))).name(name);
+ return cache(Key.get(type, Names.named(name)), type).name(name);
}
- private <K, V> UnnamedCacheBinding<K, V> core(final Key<Cache<K, V>> key) {
- final boolean disk = false;
- final CacheProvider<K, V> b = new CacheProvider<K, V>(disk, this);
- bind(key).toProvider(b).in(Scopes.SINGLETON);
- return b;
- }
-
- /**
- * Declare an unnamed in-memory/on-disk cache.
- *
- * @param <K> type of key used to find entries, must be {@link Serializable}.
- * @param <V> type of value stored by the cache, must be {@link Serializable}.
- * @param type type literal for the cache, this literal will be used to match
- * injection sites. Injection sites are matched by this type literal
- * and with {@code @Named} annotations.
- * @return binding to describe the cache. Caller must set at least the name on
- * the returned binding.
- */
- protected <K extends Serializable, V extends Serializable> UnnamedCacheBinding<K, V> disk(
+ private <K, V> UnnamedCacheBinding<K, V> cache(final Key<Cache<K, V>> key,
final TypeLiteral<Cache<K, V>> type) {
- return disk(Key.get(type));
- }
-
- /**
- * Declare a named in-memory/on-disk cache.
- *
- * @param <K> type of key used to find entries, must be {@link Serializable}.
- * @param <V> type of value stored by the cache, must be {@link Serializable}.
- * @param type type literal for the cache, this literal will be used to match
- * injection sites. Injection sites are matched by this type literal
- * and with {@code @Named} annotations.
- * @return binding to describe the cache.
- */
- protected <K extends Serializable, V extends Serializable> NamedCacheBinding<K, V> disk(
- final TypeLiteral<Cache<K, V>> type, final String name) {
- return disk(Key.get(type, Names.named(name))).name(name);
- }
-
- private <K, V> UnnamedCacheBinding<K, V> disk(final Key<Cache<K, V>> key) {
- final boolean disk = true;
- final CacheProvider<K, V> b = new CacheProvider<K, V>(disk, this);
+ final boolean disk = false;
+ final CacheProvider<K, V> b = new CacheProvider<K, V>(this, type);
bind(key).toProvider(b).in(Scopes.SINGLETON);
return b;
}
@@ -110,6 +71,12 @@
return getProvider(key);
}
+ <V> Provider<V> getValueProvider(Class<V> type) {
+ Key<V> key = Key.get(type, UniqueAnnotations.create());
+ bind(key).to(type);
+ return getProvider(key);
+ }
+
@SuppressWarnings("unchecked")
private static <K, V> Key<EntryCreator<K, V>> newKey() {
return (Key<EntryCreator<K, V>>) newKeyImpl();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java
index 5230ff6..3370b08 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CachePool.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,237 +14,6 @@
package com.google.gerrit.server.cache;
-import static java.util.concurrent.TimeUnit.MINUTES;
-import static java.util.concurrent.TimeUnit.SECONDS;
-
-import com.google.gerrit.lifecycle.LifecycleListener;
-import com.google.gerrit.server.config.ConfigUtil;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.inject.Inject;
-import com.google.inject.ProvisionException;
-import com.google.inject.Singleton;
-
-import net.sf.ehcache.CacheManager;
-import net.sf.ehcache.config.CacheConfiguration;
-import net.sf.ehcache.config.Configuration;
-import net.sf.ehcache.config.DiskStoreConfiguration;
-import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
-
-import org.eclipse.jgit.lib.Config;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-
-/** Pool of all declared caches created by {@link CacheModule}s. */
-@Singleton
-public class CachePool {
- private static final Logger log = LoggerFactory.getLogger(CachePool.class);
-
- public static class Lifecycle implements LifecycleListener {
- private final CachePool cachePool;
-
- @Inject
- Lifecycle(final CachePool cachePool) {
- this.cachePool = cachePool;
- }
-
- @Override
- public void start() {
- cachePool.start();
- }
-
- @Override
- public void stop() {
- cachePool.stop();
- }
- }
-
- private final Config config;
- private final SitePaths site;
-
- private final Object lock = new Object();
- private final Map<String, CacheProvider<?, ?>> caches;
- private CacheManager manager;
-
- @Inject
- CachePool(@GerritServerConfig final Config cfg, final SitePaths site) {
- this.config = cfg;
- this.site = site;
- this.caches = new HashMap<String, CacheProvider<?, ?>>();
- }
-
- private void start() {
- synchronized (lock) {
- if (manager != null) {
- throw new IllegalStateException("Cache pool has already been started");
- }
-
- try {
- System.setProperty("net.sf.ehcache.skipUpdateCheck", "" + true);
- } catch (SecurityException e) {
- // Ignore it, the system is just going to ping some external page
- // using a background thread and there's not much we can do about
- // it now.
- }
-
- manager = new CacheManager(new Factory().toConfiguration());
- for (CacheProvider<?, ?> p : caches.values()) {
- p.bind(manager.getEhcache(p.getName()));
- }
- }
- }
-
- private void stop() {
- synchronized (lock) {
- if (manager != null) {
- manager.shutdown();
- }
- }
- }
-
- /** <i>Discouraged</i> Get the underlying cache descriptions, for statistics. */
- public CacheManager getCacheManager() {
- synchronized (lock) {
- return manager;
- }
- }
-
- <K, V> ProxyEhcache register(final CacheProvider<K, V> provider) {
- synchronized (lock) {
- if (manager != null) {
- throw new IllegalStateException("Cache pool has already been started");
- }
-
- final String n = provider.getName();
- if (caches.containsKey(n) && caches.get(n) != provider) {
- throw new IllegalStateException("Cache \"" + n + "\" already defined");
- }
- caches.put(n, provider);
- return new ProxyEhcache(n);
- }
- }
-
- private class Factory {
- private static final int MB = 1024 * 1024;
- private final Configuration mgr = new Configuration();
-
- Configuration toConfiguration() {
- configureDiskStore();
- configureDefaultCache();
-
- for (CacheProvider<?, ?> p : caches.values()) {
- final String name = p.getName();
- final CacheConfiguration c = newCache(name);
- c.setMemoryStoreEvictionPolicyFromObject(toPolicy(p.evictionPolicy()));
-
- c.setMaxElementsInMemory(getInt(name, "memorylimit", p.memoryLimit()));
-
- c.setTimeToIdleSeconds(0);
- c.setTimeToLiveSeconds(getSeconds(name, "maxage", p.maxAge()));
- c.setEternal(c.getTimeToLiveSeconds() == 0);
-
- if (p.disk() && mgr.getDiskStoreConfiguration() != null) {
- c.setMaxElementsOnDisk(getInt(name, "disklimit", p.diskLimit()));
-
- int v = c.getDiskSpoolBufferSizeMB() * MB;
- v = getInt(name, "diskbuffer", v) / MB;
- c.setDiskSpoolBufferSizeMB(Math.max(1, v));
- c.setOverflowToDisk(c.getMaxElementsOnDisk() > 0);
- c.setDiskPersistent(c.getMaxElementsOnDisk() > 0);
- }
-
- mgr.addCache(c);
- }
-
- return mgr;
- }
-
- private MemoryStoreEvictionPolicy toPolicy(final EvictionPolicy policy) {
- switch (policy) {
- case LFU:
- return MemoryStoreEvictionPolicy.LFU;
-
- case LRU:
- return MemoryStoreEvictionPolicy.LRU;
-
- default:
- throw new IllegalArgumentException("Unsupported " + policy);
- }
- }
-
- private int getInt(String n, String s, int d) {
- return config.getInt("cache", n, s, d);
- }
-
- private long getSeconds(String n, String s, long d) {
- d = MINUTES.convert(d, SECONDS);
- long m = ConfigUtil.getTimeUnit(config, "cache", n, s, d, MINUTES);
- return SECONDS.convert(m, MINUTES);
- }
-
- private void configureDiskStore() {
- boolean needDisk = false;
- for (CacheProvider<?, ?> p : caches.values()) {
- if (p.disk()) {
- needDisk = true;
- break;
- }
- }
- if (!needDisk) {
- return;
- }
-
- File loc = site.resolve(config.getString("cache", null, "directory"));
- if (loc == null) {
- } else if (loc.exists() || loc.mkdirs()) {
- if (loc.canWrite()) {
- final DiskStoreConfiguration c = new DiskStoreConfiguration();
- c.setPath(loc.getAbsolutePath());
- mgr.addDiskStore(c);
- log.info("Enabling disk cache " + loc.getAbsolutePath());
- } else {
- log.warn("Can't write to disk cache: " + loc.getAbsolutePath());
- }
- } else {
- log.warn("Can't create disk cache: " + loc.getAbsolutePath());
- }
- }
-
- private void configureDefaultCache() {
- final CacheConfiguration c = new CacheConfiguration();
-
- c.setMaxElementsInMemory(1024);
- c.setMemoryStoreEvictionPolicyFromObject(MemoryStoreEvictionPolicy.LFU);
-
- c.setTimeToIdleSeconds(0);
- c.setTimeToLiveSeconds(0 /* infinite */);
- c.setEternal(true);
-
- if (mgr.getDiskStoreConfiguration() != null) {
- c.setMaxElementsOnDisk(16384);
- c.setOverflowToDisk(false);
- c.setDiskPersistent(false);
-
- c.setDiskSpoolBufferSizeMB(5);
- c.setDiskExpiryThreadIntervalSeconds(60 * 60);
- }
-
- mgr.setDefaultCacheConfiguration(c);
- }
-
- private CacheConfiguration newCache(final String name) {
- try {
- final CacheConfiguration c;
- c = mgr.getDefaultCacheConfiguration().clone();
- c.setName(name);
- return c;
- } catch (CloneNotSupportedException e) {
- throw new ProvisionException("Cannot configure cache " + name, e);
- }
- }
- }
+public interface CachePool {
+ public <K, V> ProxyCache<K, V> register(CacheProvider<K, V> provider);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheProvider.java
index 0ba424f..72973e1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/CacheProvider.java
@@ -18,36 +18,67 @@
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.SECONDS;
+import com.google.gwtorm.protobuf.CodecFactory;
+import com.google.gwtorm.protobuf.ProtobufCodec;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
+import com.google.inject.TypeLiteral;
-import net.sf.ehcache.Ehcache;
-
+import java.lang.reflect.Constructor;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
import java.util.concurrent.TimeUnit;
-final class CacheProvider<K, V> implements Provider<Cache<K, V>>,
+public final class CacheProvider<K, V> implements Provider<Cache<K, V>>,
NamedCacheBinding<K, V>, UnnamedCacheBinding<K, V> {
private final CacheModule module;
- private final boolean disk;
private int memoryLimit;
private int diskLimit;
private long maxAge;
private EvictionPolicy evictionPolicy;
private String cacheName;
- private ProxyEhcache cache;
+ private ProxyCache<K, V> cache;
private Provider<EntryCreator<K, V>> entryCreator;
+ private Class<K> keyClass;
+ private Class<V> valueClass;
+ private Provider<V> valueProvider;
- CacheProvider(final boolean disk, CacheModule module) {
- this.disk = disk;
+ @SuppressWarnings("unchecked")
+ CacheProvider(CacheModule module, TypeLiteral<Cache<K, V>> typeLiteral) {
this.module = module;
memoryLimit(1024);
maxAge(90, DAYS);
evictionPolicy(LFU);
- if (disk) {
- diskLimit(16384);
+ Type[] tmp =
+ ((ParameterizedType) typeLiteral.getType()).getActualTypeArguments();
+
+ keyClass = (Class<K>) tmp[0];
+ valueClass = (Class<V>) tmp[1];
+
+ for (Constructor c : valueClass.getDeclaredConstructors()) {
+ if (c.getAnnotation(Inject.class) != null) {
+ valueProvider = module.getValueProvider(valueClass);
+ }
+ }
+
+ try {
+ ProtobufCodec<K> keyCodec = CodecFactory.encoder(keyClass);
+ keyCodec.decode(new byte[0]);
+ } catch (RuntimeException err) {
+ throw new IllegalStateException("Cannot support " + keyClass
+ + " in protobuf format", err);
+ }
+ if (valueProvider == null) {
+ try {
+ ProtobufCodec<V> valueCodec = CodecFactory.encoder(valueClass);
+ valueCodec.decode(new byte[0]);
+ } catch (RuntimeException err) {
+ throw new IllegalStateException("Cannot support " + valueClass
+ + " in protobuf format", err);
+ }
}
}
@@ -56,34 +87,53 @@
this.cache = pool.register(this);
}
- void bind(final Ehcache ehcache) {
- cache.bind(ehcache);
+ public void bind(Cache<K, V> impl) {
+ if (cache == null) {
+ throw new ProvisionException("Cache was never registered");
+ }
+ cache.bind(impl);
}
- String getName() {
+ public EntryCreator<K, V> getEntryCreator() {
+ return entryCreator != null ? entryCreator.get() : null;
+ }
+
+ public Provider<V> getValueProvider() {
+ return valueProvider;
+ }
+
+ public String getName() {
if (cacheName == null) {
throw new ProvisionException("Cache has no name");
}
return cacheName;
}
- boolean disk() {
- return disk;
+ public boolean disk() {
+ return diskLimit() > 0;
}
- int memoryLimit() {
+ public int memoryLimit() {
return memoryLimit;
}
- int diskLimit() {
+ public int diskLimit() {
return diskLimit;
}
- long maxAge() {
+ public long maxAge() {
return maxAge;
}
- EvictionPolicy evictionPolicy() {
+ public Class<K> getKeyClass() {
+ return keyClass;
+ }
+
+ public Class<V> getValueClass() {
+ return valueClass;
+ }
+
+ public EvictionPolicy evictionPolicy() {
return evictionPolicy;
}
@@ -101,13 +151,6 @@
}
public NamedCacheBinding<K, V> diskLimit(final int objects) {
- if (!disk) {
- // TODO This should really be a compile time type error, but I'm
- // too lazy to create the mess of permutations required to setup
- // type safe returns for bindings in our little DSL.
- //
- throw new IllegalStateException("Cache is not disk based");
- }
diskLimit = objects;
return this;
}
@@ -133,9 +176,6 @@
if (cache == null) {
throw new ProvisionException("Cache \"" + cacheName + "\" not available");
}
- if (entryCreator != null) {
- return new PopulatingCache<K, V>(cache, entryCreator.get());
- }
- return new SimpleCache<K, V>(cache);
+ return cache;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/ProxyCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/ProxyCache.java
new file mode 100644
index 0000000..1f18f3a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/ProxyCache.java
@@ -0,0 +1,53 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.cache;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.concurrent.TimeUnit;
+
+/** Proxy around a cache which has not yet been created. */
+public final class ProxyCache<K, V> implements Cache<K, V> {
+ private volatile Cache<K, V> self;
+
+ public void bind(Cache<K, V> self) {
+ this.self = self;
+ }
+
+ @Override
+ public ListenableFuture<V> get(K key) {
+ return self.get(key);
+ }
+
+ @Override
+ public ListenableFuture<Void> putAsync(K key, V value) {
+ return self.putAsync(key, value);
+ }
+
+ @Override
+ public ListenableFuture<Void> removeAsync(K key) {
+ return self.removeAsync(key);
+ }
+
+ @Override
+ public ListenableFuture<Void> removeAllAsync() {
+ return self.removeAllAsync();
+ }
+
+ @Override
+ public long getTimeToLive(TimeUnit unit) {
+ return self.getTimeToLive(unit);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/ProxyEhcache.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/ProxyEhcache.java
deleted file mode 100644
index 8b3179a..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/ProxyEhcache.java
+++ /dev/null
@@ -1,393 +0,0 @@
-// Copyright (C) 2008 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.cache;
-
-import net.sf.ehcache.CacheException;
-import net.sf.ehcache.CacheManager;
-import net.sf.ehcache.Ehcache;
-import net.sf.ehcache.Element;
-import net.sf.ehcache.Statistics;
-import net.sf.ehcache.Status;
-import net.sf.ehcache.bootstrap.BootstrapCacheLoader;
-import net.sf.ehcache.config.CacheConfiguration;
-import net.sf.ehcache.event.RegisteredEventListeners;
-import net.sf.ehcache.exceptionhandler.CacheExceptionHandler;
-import net.sf.ehcache.extension.CacheExtension;
-import net.sf.ehcache.loader.CacheLoader;
-import net.sf.ehcache.statistics.CacheUsageListener;
-import net.sf.ehcache.statistics.LiveCacheStatistics;
-import net.sf.ehcache.statistics.sampled.SampledCacheStatistics;
-
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/** Proxy around a cache which has not yet been created. */
-final class ProxyEhcache implements Ehcache {
- private final String cacheName;
- private volatile Ehcache self;
-
- ProxyEhcache(final String cacheName) {
- this.cacheName = cacheName;
- }
-
- void bind(final Ehcache self) {
- this.self = self;
- }
-
- private Ehcache self() {
- return self;
- }
-
- @Override
- public Object clone() throws CloneNotSupportedException {
- throw new CloneNotSupportedException();
- }
-
- @Override
- public String getName() {
- return cacheName;
- }
-
- @Override
- public void setName(String name) {
- throw new UnsupportedOperationException();
- }
-
- //
- // Everything else delegates through self.
- //
-
- public void bootstrap() {
- self().bootstrap();
- }
-
- public long calculateInMemorySize() throws IllegalStateException,
- CacheException {
- return self().calculateInMemorySize();
- }
-
- public void clearStatistics() {
- self().clearStatistics();
- }
-
- public void dispose() throws IllegalStateException {
- self().dispose();
- }
-
- public void evictExpiredElements() {
- self().evictExpiredElements();
- }
-
- public void flush() throws IllegalStateException, CacheException {
- self().flush();
- }
-
- public Element get(Object key) throws IllegalStateException, CacheException {
- return self().get(key);
- }
-
- public Element get(Serializable key) throws IllegalStateException,
- CacheException {
- return self().get(key);
- }
-
- @SuppressWarnings("unchecked")
- public Map getAllWithLoader(Collection keys, Object loaderArgument)
- throws CacheException {
- return self().getAllWithLoader(keys, loaderArgument);
- }
-
- public float getAverageGetTime() {
- return self().getAverageGetTime();
- }
-
- public BootstrapCacheLoader getBootstrapCacheLoader() {
- return self().getBootstrapCacheLoader();
- }
-
- public CacheConfiguration getCacheConfiguration() {
- if (self == null) {
- // In Ehcache 1.7, BlockingCache wants to ask us if we are
- // clustered using Terracotta. Unfortunately it is too early
- // to know for certain as the caches have not actually been
- // created or configured.
- //
- return new CacheConfiguration();
- }
- return self().getCacheConfiguration();
- }
-
- public RegisteredEventListeners getCacheEventNotificationService() {
- return self().getCacheEventNotificationService();
- }
-
- public CacheExceptionHandler getCacheExceptionHandler() {
- return self().getCacheExceptionHandler();
- }
-
- public CacheManager getCacheManager() {
- return self().getCacheManager();
- }
-
- public int getDiskStoreSize() throws IllegalStateException {
- return self().getDiskStoreSize();
- }
-
- public String getGuid() {
- return self().getGuid();
- }
-
- @SuppressWarnings("unchecked")
- public List getKeys() throws IllegalStateException, CacheException {
- return self().getKeys();
- }
-
- @SuppressWarnings("unchecked")
- public List getKeysNoDuplicateCheck() throws IllegalStateException {
- return self().getKeysNoDuplicateCheck();
- }
-
- @SuppressWarnings("unchecked")
- public List getKeysWithExpiryCheck() throws IllegalStateException,
- CacheException {
- return self().getKeysWithExpiryCheck();
- }
-
- public long getMemoryStoreSize() throws IllegalStateException {
- return self().getMemoryStoreSize();
- }
-
- public Element getQuiet(Object key) throws IllegalStateException,
- CacheException {
- return self().getQuiet(key);
- }
-
- public Element getQuiet(Serializable key) throws IllegalStateException,
- CacheException {
- return self().getQuiet(key);
- }
-
- public List<CacheExtension> getRegisteredCacheExtensions() {
- return self().getRegisteredCacheExtensions();
- }
-
- public List<CacheLoader> getRegisteredCacheLoaders() {
- return self().getRegisteredCacheLoaders();
- }
-
- public int getSize() throws IllegalStateException, CacheException {
- return self().getSize();
- }
-
- public Statistics getStatistics() throws IllegalStateException {
- return self().getStatistics();
- }
-
- public int getStatisticsAccuracy() {
- return self().getStatisticsAccuracy();
- }
-
- public Status getStatus() {
- return self().getStatus();
- }
-
- public Element getWithLoader(Object key, CacheLoader loader,
- Object loaderArgument) throws CacheException {
- return self().getWithLoader(key, loader, loaderArgument);
- }
-
- public void initialise() {
- self().initialise();
- }
-
- public boolean isDisabled() {
- return self().isDisabled();
- }
-
- public boolean isElementInMemory(Object key) {
- return self().isElementInMemory(key);
- }
-
- public boolean isElementInMemory(Serializable key) {
- return self().isElementInMemory(key);
- }
-
- public boolean isElementOnDisk(Object key) {
- return self().isElementOnDisk(key);
- }
-
- public boolean isElementOnDisk(Serializable key) {
- return self().isElementOnDisk(key);
- }
-
- public boolean isExpired(Element element) throws IllegalStateException,
- NullPointerException {
- return self().isExpired(element);
- }
-
- public boolean isKeyInCache(Object key) {
- return self().isKeyInCache(key);
- }
-
- public boolean isValueInCache(Object value) {
- return self().isValueInCache(value);
- }
-
- public void load(Object key) throws CacheException {
- self().load(key);
- }
-
- @SuppressWarnings("unchecked")
- public void loadAll(Collection keys, Object argument) throws CacheException {
- self().loadAll(keys, argument);
- }
-
- public void put(Element element, boolean doNotNotifyCacheReplicators)
- throws IllegalArgumentException, IllegalStateException, CacheException {
- self().put(element, doNotNotifyCacheReplicators);
- }
-
- public void put(Element element) throws IllegalArgumentException,
- IllegalStateException, CacheException {
- self().put(element);
- }
-
- public void putQuiet(Element element) throws IllegalArgumentException,
- IllegalStateException, CacheException {
- self().putQuiet(element);
- }
-
- public void registerCacheExtension(CacheExtension cacheExtension) {
- self().registerCacheExtension(cacheExtension);
- }
-
- public void registerCacheLoader(CacheLoader cacheLoader) {
- self().registerCacheLoader(cacheLoader);
- }
-
- public boolean remove(Object key, boolean doNotNotifyCacheReplicators)
- throws IllegalStateException {
- return self().remove(key, doNotNotifyCacheReplicators);
- }
-
- public boolean remove(Object key) throws IllegalStateException {
- return self().remove(key);
- }
-
- public boolean remove(Serializable key, boolean doNotNotifyCacheReplicators)
- throws IllegalStateException {
- return self().remove(key, doNotNotifyCacheReplicators);
- }
-
- public boolean remove(Serializable key) throws IllegalStateException {
- return self().remove(key);
- }
-
- public void removeAll() throws IllegalStateException, CacheException {
- self().removeAll();
- }
-
- public void removeAll(boolean doNotNotifyCacheReplicators)
- throws IllegalStateException, CacheException {
- self().removeAll(doNotNotifyCacheReplicators);
- }
-
- public boolean removeQuiet(Object key) throws IllegalStateException {
- return self().removeQuiet(key);
- }
-
- public boolean removeQuiet(Serializable key) throws IllegalStateException {
- return self().removeQuiet(key);
- }
-
- public void setBootstrapCacheLoader(BootstrapCacheLoader bootstrapCacheLoader)
- throws CacheException {
- self().setBootstrapCacheLoader(bootstrapCacheLoader);
- }
-
- public void setCacheExceptionHandler(
- CacheExceptionHandler cacheExceptionHandler) {
- self().setCacheExceptionHandler(cacheExceptionHandler);
- }
-
- public void setCacheManager(CacheManager cacheManager) {
- self().setCacheManager(cacheManager);
- }
-
- public void setDisabled(boolean disabled) {
- self().setDisabled(disabled);
- }
-
- public void setDiskStorePath(String diskStorePath) throws CacheException {
- self().setDiskStorePath(diskStorePath);
- }
-
- public void setStatisticsAccuracy(int statisticsAccuracy) {
- self().setStatisticsAccuracy(statisticsAccuracy);
- }
-
- public void unregisterCacheExtension(CacheExtension cacheExtension) {
- self().unregisterCacheExtension(cacheExtension);
- }
-
- public void unregisterCacheLoader(CacheLoader cacheLoader) {
- self().unregisterCacheLoader(cacheLoader);
- }
-
- public Object getInternalContext() {
- return self.getInternalContext();
- }
-
- public LiveCacheStatistics getLiveCacheStatistics() throws IllegalStateException {
- return self.getLiveCacheStatistics();
- }
-
- public SampledCacheStatistics getSampledCacheStatistics() {
- return self.getSampledCacheStatistics();
- }
-
- public int getSizeBasedOnAccuracy(int statisticsAccuracy) throws IllegalArgumentException,
- IllegalStateException, CacheException {
- return self.getSizeBasedOnAccuracy(statisticsAccuracy);
- }
-
- public boolean isSampledStatisticsEnabled() {
- return self.isSampledStatisticsEnabled();
- }
-
- public boolean isStatisticsEnabled() {
- return self.isStatisticsEnabled();
- }
-
- public void registerCacheUsageListener(CacheUsageListener cacheUsageListener)
- throws IllegalStateException {
- self.registerCacheUsageListener(cacheUsageListener);
- }
-
- public void removeCacheUsageListener(CacheUsageListener cacheUsageListener)
- throws IllegalStateException {
- self.removeCacheUsageListener(cacheUsageListener);
- }
-
- public void setSampledStatisticsEnabled(boolean enableStatistics) {
- self.setSampledStatisticsEnabled(enableStatistics);
- }
-
- public void setStatisticsEnabled(boolean enableStatistics) {
- self.setStatisticsEnabled(enableStatistics);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 232c09d..5ebaada 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -29,20 +29,20 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.MimeUtilFileTypeRegistry;
import com.google.gerrit.server.ReplicationUser;
-import com.google.gerrit.server.account.AccountByEmailCacheImpl;
+import com.google.gerrit.server.StarredChangesCacheImpl;
+import com.google.gerrit.server.account.AccountAgreementsCacheImpl;
import com.google.gerrit.server.account.AccountCacheImpl;
+import com.google.gerrit.server.account.AccountDiffPreferencesCacheImpl;
+import com.google.gerrit.server.account.AccountGroupAgreementsCacheImpl;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
+import com.google.gerrit.server.account.AccountProjectWatchCacheImpl;
import com.google.gerrit.server.account.DefaultRealm;
import com.google.gerrit.server.account.EmailExpander;
import com.google.gerrit.server.account.GroupCacheImpl;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.auth.ldap.LdapModule;
-import com.google.gerrit.server.cache.CachePool;
-import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.git.ChangeMergeQueue;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.LocalDiskRepositoryManager;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gerrit.server.git.PushAllProjectsOp;
import com.google.gerrit.server.git.PushReplication;
@@ -50,10 +50,8 @@
import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.mail.EmailSender;
import com.google.gerrit.server.mail.FromAddressGenerator;
import com.google.gerrit.server.mail.FromAddressGeneratorProvider;
-import com.google.gerrit.server.mail.SmtpEmailSender;
import com.google.gerrit.server.patch.PatchListCacheImpl;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ChangeControl;
@@ -68,7 +66,6 @@
import org.apache.velocity.app.Velocity;
import org.apache.velocity.runtime.RuntimeConstants;
-
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
@@ -150,17 +147,19 @@
GerritPersonIdentProvider.class);
bind(IdGenerator.class);
- bind(CachePool.class);
- install(AccountByEmailCacheImpl.module());
install(AccountCacheImpl.module());
install(GroupCacheImpl.module());
install(PatchListCacheImpl.module());
install(ProjectCacheImpl.module());
+ install(StarredChangesCacheImpl.module());
+ install(AccountProjectWatchCacheImpl.module());
+ install(AccountAgreementsCacheImpl.module());
+ install(AccountGroupAgreementsCacheImpl.module());
+ install(AccountDiffPreferencesCacheImpl.module());
factory(AccountInfoCacheFactory.Factory.class);
- factory(ProjectState.Factory.class);
+ bind(ProjectState.class);
- bind(GitRepositoryManager.class).to(LocalDiskRepositoryManager.class);
bind(FileTypeRegistry.class).to(MimeUtilFileTypeRegistry.class);
bind(WorkQueue.class);
bind(ToolsCatalog.class);
@@ -175,7 +174,6 @@
bind(FromAddressGenerator.class).toProvider(
FromAddressGeneratorProvider.class).in(SINGLETON);
- bind(EmailSender.class).to(SmtpEmailSender.class).in(SINGLETON);
bind(PatchSetInfoFactory.class);
bind(IdentifiedUser.GenericFactory.class).in(SINGLETON);
@@ -187,8 +185,6 @@
install(new LifecycleModule() {
@Override
protected void configure() {
- listener().to(LocalDiskRepositoryManager.Lifecycle.class);
- listener().to(CachePool.Lifecycle.class);
listener().to(WorkQueue.Lifecycle.class);
listener().to(VelocityLifecycle.class);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
index 5a75995..66b67a8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
@@ -34,8 +34,7 @@
import com.google.gerrit.server.patch.PublishComments;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.query.change.ChangeQueryBuilder;
-import com.google.gerrit.server.query.change.ChangeQueryRewriter;
+import com.google.gerrit.server.query.change.ChangeQueryModule;
import com.google.inject.servlet.RequestScoped;
/** Bindings for {@link RequestScoped} entities. */
@@ -47,13 +46,12 @@
RequestScoped.class);
bind(IdentifiedUser.RequestFactory.class).in(SINGLETON);
bind(AccountResolver.class);
- bind(ChangeQueryRewriter.class);
+ install(new ChangeQueryModule());
bind(ChangeControl.Factory.class).in(SINGLETON);
bind(GroupControl.Factory.class).in(SINGLETON);
bind(ProjectControl.Factory.class).in(SINGLETON);
- factory(ChangeQueryBuilder.Factory.class);
factory(ReceiveCommits.Factory.class);
factory(MergeOp.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/RequestScopedReviewDbProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/RequestScopedReviewDbProvider.java
index 5aa78cb..9c3da74 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/RequestScopedReviewDbProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/RequestScopedReviewDbProvider.java
@@ -17,7 +17,7 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.RequestCleanup;
import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.jdbc.Database;
+import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
@@ -26,11 +26,11 @@
/** Provides {@link ReviewDb} database handle live only for this request. */
@Singleton
final class RequestScopedReviewDbProvider implements Provider<ReviewDb> {
- private final Database<ReviewDb> schema;
+ private final SchemaFactory<ReviewDb> schema;
private final Provider<RequestCleanup> cleanup;
@Inject
- RequestScopedReviewDbProvider(final Database<ReviewDb> schema,
+ RequestScopedReviewDbProvider(final SchemaFactory<ReviewDb> schema,
final Provider<RequestCleanup> cleanup) {
this.schema = schema;
this.cleanup = cleanup;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java
index da17c08..4b6dcaf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java
@@ -14,10 +14,9 @@
package com.google.gerrit.server.contact;
-import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
-import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
@@ -33,14 +32,14 @@
public class ContactStoreProvider implements Provider<ContactStore> {
private final Config config;
private final SitePaths site;
- private final SchemaFactory<ReviewDb> schema;
+ private final AccountCache accounts;
@Inject
ContactStoreProvider(@GerritServerConfig final Config config,
- final SitePaths site, final SchemaFactory<ReviewDb> schema) {
+ final SitePaths site, final AccountCache accountCache) {
this.config = config;
this.site = site;
- this.schema = schema;
+ this.accounts = accountCache;
}
@Override
@@ -68,7 +67,7 @@
throw new ProvisionException("PGP public key file \""
+ pubkey.getAbsolutePath() + "\" not found");
}
- return new EncryptedContactStore(storeUrl, storeAPPSEC, pubkey, schema);
+ return new EncryptedContactStore(storeUrl, storeAPPSEC, pubkey, accounts);
}
private static boolean havePGP() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java
index 05d4e7a..34185dc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java
@@ -18,10 +18,10 @@
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.ContactInformation;
-import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.UrlEncoded;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.client.SchemaFactory;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.util.FutureException;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
@@ -65,18 +65,18 @@
LoggerFactory.getLogger(EncryptedContactStore.class);
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
- private final SchemaFactory<ReviewDb> schema;
private final PGPPublicKey dest;
private final SecureRandom prng;
private final URL storeUrl;
private final String storeAPPSEC;
+ private final AccountCache accountCache;
EncryptedContactStore(final URL storeUrl, final String storeAPPSEC,
- final File pubKey, final SchemaFactory<ReviewDb> schema) {
+ final File pubKey, final AccountCache accountCache) {
this.storeUrl = storeUrl;
this.storeAPPSEC = storeAPPSEC;
- this.schema = schema;
this.dest = selectKey(readPubRing(pubKey));
+ this.accountCache = accountCache;
final String prngName = "SHA1PRNG";
try {
@@ -250,31 +250,26 @@
field(b, "Preferred-Email", account.getPreferredEmail());
try {
- final ReviewDb db = schema.open();
- try {
- for (final AccountExternalId e : db.accountExternalIds().byAccount(
- account.getId())) {
- final StringBuilder oistr = new StringBuilder();
- if (e.getEmailAddress() != null && e.getEmailAddress().length() > 0) {
- if (oistr.length() > 0) {
- oistr.append(' ');
- }
- oistr.append(e.getEmailAddress());
+ for (AccountExternalId e : FutureUtil.get(
+ accountCache.get(account.getId())).getExternalIds()) {
+ final StringBuilder oistr = new StringBuilder();
+ if (e.getEmailAddress() != null && e.getEmailAddress().length() > 0) {
+ if (oistr.length() > 0) {
+ oistr.append(' ');
}
- if (e.isScheme(AccountExternalId.SCHEME_MAILTO)) {
- if (oistr.length() > 0) {
- oistr.append(' ');
- }
- oistr.append('<');
- oistr.append(e.getExternalId());
- oistr.append('>');
- }
- field(b, "Identity", oistr.toString());
+ oistr.append(e.getEmailAddress());
}
- } finally {
- db.close();
+ if (e.isScheme(AccountExternalId.SCHEME_MAILTO)) {
+ if (oistr.length() > 0) {
+ oistr.append(' ');
+ }
+ oistr.append('<');
+ oistr.append(e.getExternalId());
+ oistr.append('>');
+ }
+ field(b, "Identity", oistr.toString());
}
- } catch (OrmException e) {
+ } catch (FutureException e) {
throw new ContactInformationStoreException(e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
index 3ad397e..052ea34 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -23,6 +23,7 @@
import com.google.gerrit.reviewdb.TrackingId;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -143,7 +144,7 @@
* @return object suitable for serialization to JSON
*/
public AccountAttribute asAccountAttribute(Account.Id id) {
- return asAccountAttribute(accountCache.get(id).getAccount());
+ return asAccountAttribute(FutureUtil.get(accountCache.getAccount(id)));
}
/**
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
index b8cfb71..9b29b84 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
@@ -15,8 +15,10 @@
package com.google.gerrit.server.git;
import com.google.gerrit.lifecycle.LifecycleListener;
+import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -48,6 +50,20 @@
private static final String UNNAMED =
"Unnamed repository; edit this file to name it for gitweb.";
+ public static class Module extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(GitRepositoryManager.class).to(LocalDiskRepositoryManager.class);
+
+ install(new LifecycleModule() {
+ @Override
+ protected void configure() {
+ listener().to(LocalDiskRepositoryManager.Lifecycle.class);
+ }
+ });
+ }
+ }
+
public static class Lifecycle implements LifecycleListener {
private final Config cfg;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 2898369..f93d2d5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -42,6 +42,7 @@
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gerrit.server.workflow.CategoryFunction;
import com.google.gerrit.server.workflow.FunctionState;
import com.google.gwtorm.client.AtomicUpdate;
@@ -190,7 +191,8 @@
}
public void merge() throws MergeException {
- final ProjectState pe = projectCache.get(destBranch.getParentKey());
+ ProjectState pe =
+ FutureUtil.get(projectCache.get(destBranch.getParentKey()));
if (pe == null) {
throw new MergeException("No such project: " + destBranch.getParentKey());
}
@@ -1202,7 +1204,7 @@
try {
hooks.doChangeMergedHook(c, //
- accountCache.get(submitter.getAccountId()).getAccount(), //
+ FutureUtil.get(accountCache.getAccount(submitter.getAccountId())), //
schema.patchSets().get(c.currentPatchSetId()));
} catch (OrmException ex) {
log.error("Cannot run hook for submitted patch set " + c.getId(), ex);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index fbcdc09..a166b1e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.git;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.common.ChangeHookRunner;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.ApprovalType;
@@ -21,9 +23,7 @@
import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.reviewdb.AbstractAgreement;
import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.reviewdb.AccountAgreement;
import com.google.gerrit.reviewdb.AccountGroup;
-import com.google.gerrit.reviewdb.AccountGroupAgreement;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Change;
@@ -39,6 +39,8 @@
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountAgreementsCache;
+import com.google.gerrit.server.account.AccountGroupAgreementsCache;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.TrackingFooters;
@@ -50,6 +52,7 @@
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.AtomicUpdate;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
@@ -140,6 +143,8 @@
private final String canonicalWebUrl;
private final PersonIdent gerritIdent;
private final TrackingFooters trackingFooters;
+ private final AccountAgreementsCache accountAgreementsCache;
+ private final AccountGroupAgreementsCache accountGroupAgreementsCache;
private final ProjectControl projectControl;
private final Project project;
@@ -172,6 +177,8 @@
@CanonicalWebUrl @Nullable final String canonicalWebUrl,
@GerritPersonIdent final PersonIdent gerritIdent,
final TrackingFooters trackingFooters,
+ final AccountAgreementsCache accountAgreementsCache,
+ final AccountGroupAgreementsCache accountGroupAgreementsCache,
@Assisted final ProjectControl projectControl,
@Assisted final Repository repo) {
@@ -188,6 +195,8 @@
this.canonicalWebUrl = canonicalWebUrl;
this.gerritIdent = gerritIdent;
this.trackingFooters = trackingFooters;
+ this.accountAgreementsCache = accountAgreementsCache;
+ this.accountGroupAgreementsCache = accountGroupAgreementsCache;
this.projectControl = projectControl;
this.project = projectControl.getProject();
@@ -296,40 +305,12 @@
AbstractAgreement bestAgreement = null;
ContributorAgreement bestCla = null;
- OUTER: for (AccountGroup.Id groupId : currentUser.getEffectiveGroups()) {
- final List<AccountGroupAgreement> temp =
- db.accountGroupAgreements().byGroup(groupId).toList();
-
- Collections.reverse(temp);
-
- for (final AccountGroupAgreement a : temp) {
- final ContributorAgreement cla =
- db.contributorAgreements().get(a.getAgreementId());
- if (cla == null) {
- continue;
- }
-
+ List<AbstractAgreement> accepted = getAgreements();
+ Collections.sort(accepted, AbstractAgreement.SORT);
+ for (AbstractAgreement a : accepted) {
+ bestCla = db.contributorAgreements().get(a.getAgreementId());
+ if (bestCla != null) {
bestAgreement = a;
- bestCla = cla;
- break OUTER;
- }
- }
-
- if (bestAgreement == null) {
- final List<AccountAgreement> temp =
- db.accountAgreements().byAccount(currentUser.getAccountId()).toList();
-
- Collections.reverse(temp);
-
- for (final AccountAgreement a : temp) {
- final ContributorAgreement cla =
- db.contributorAgreements().get(a.getAgreementId());
- if (cla == null) {
- continue;
- }
-
- bestAgreement = a;
- bestCla = cla;
break;
}
}
@@ -404,6 +385,23 @@
return new Capable(msg.toString());
}
+ @SuppressWarnings("unchecked")
+ private List<AbstractAgreement> getAgreements() {
+ ListenableFuture f;
+
+ List<ListenableFuture<List<AbstractAgreement>>> agreements =
+ Lists.newArrayList();
+
+ f = accountAgreementsCache.byAccount(currentUser.getAccountId());
+ agreements.add(f);
+
+ for (AccountGroup.Id groupId : currentUser.getEffectiveGroups()) {
+ f = accountGroupAgreementsCache.byGroup(groupId);
+ agreements.add(f);
+ }
+ return FutureUtil.getOrEmptyList(FutureUtil.concat(agreements));
+ }
+
private static boolean missing(final String value) {
return value == null || value.trim().equals("");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/Address.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/Address.java
index 1640e05..624e626 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/Address.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/Address.java
@@ -16,8 +16,8 @@
import java.io.UnsupportedEncodingException;
-class Address {
- static Address parse(final String in) {
+public class Address {
+ public static Address parse(final String in) {
final int lt = in.indexOf('<');
final int gt = in.indexOf('>');
final int at = in.indexOf("@");
@@ -37,15 +37,23 @@
final String name;
final String email;
- Address(String email) {
+ public Address(String email) {
this(null, email);
}
- Address(String name, String email) {
+ public Address(String name, String email) {
this.name = name;
this.email = email;
}
+ public String getName() {
+ return name;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
@Override
public String toString() {
try {
@@ -55,7 +63,7 @@
}
}
- String toHeaderString() throws UnsupportedEncodingException {
+ public String toHeaderString() throws UnsupportedEncodingException {
if (name != null) {
return quotedPhrase(name) + " <" + email + ">";
} else if (isSimple()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index 6e641a2..931fc37 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -14,8 +14,8 @@
package com.google.gerrit.server.mail;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.reviewdb.Account;
-import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeMessage;
@@ -32,6 +32,7 @@
import com.google.gerrit.server.query.QueryParseException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import java.util.ArrayList;
@@ -99,15 +100,12 @@
protected abstract void formatChange() throws EmailException;
/** Setup the message headers and envelope (TO, CC, BCC). */
+ @Override
protected void init() throws EmailException {
- if (args.projectCache != null) {
- projectState = args.projectCache.get(change.getProject());
- projectName =
- projectState != null ? projectState.getProject().getName() : null;
- } else {
- projectState = null;
- projectName = null;
- }
+ projectState = FutureUtil.get(args.projectCache.get(change.getProject()));
+ projectName = projectState != null //
+ ? projectState.getProject().getName() //
+ : null;
if (patchSet == null) {
try {
@@ -234,7 +232,7 @@
/** Get the patch list corresponding to this patch set. */
protected PatchList getPatchList() {
if (patchSet != null) {
- return args.patchListCache.get(change, patchSet);
+ return FutureUtil.getOrNull(args.patchListCache.get(change, patchSet));
}
return null;
}
@@ -244,14 +242,6 @@
return projectState;
}
- /** Get the groups which own the project. */
- protected Set<AccountGroup.Id> getProjectOwners() {
- final ProjectState r;
-
- r = args.projectCache.get(change.getProject());
- return r != null ? r.getOwners() : Collections.<AccountGroup.Id> emptySet();
- }
-
/** TO or CC all vested parties (change owner, patch set uploader, author). */
protected void rcptToAuthors(final RecipientType rt) {
add(rt, change.getOwner());
@@ -306,14 +296,18 @@
List<AccountProjectWatch> matching = new ArrayList<AccountProjectWatch>();
Set<Account.Id> projectWatchers = new HashSet<Account.Id>();
- for (AccountProjectWatch w : args.db.get().accountProjectWatches()
- .byProject(change.getProject())) {
+ ListenableFuture<List<AccountProjectWatch>> thisProject =
+ args.accountProjectWatchCache.byProject(change.getProject());
+
+ ListenableFuture<List<AccountProjectWatch>> allProjects =
+ args.accountProjectWatchCache.byProject(args.wildProject);
+
+ for (AccountProjectWatch w : FutureUtil.get(thisProject)) {
projectWatchers.add(w.getAccountId());
add(matching, w);
}
- for (AccountProjectWatch w : args.db.get().accountProjectWatches()
- .byProject(args.wildProject)) {
+ for (AccountProjectWatch w : FutureUtil.get(allProjects)) {
if (!projectWatchers.contains(w.getAccountId())) {
add(matching, w);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
index 18bfe97..be830b9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
@@ -19,11 +19,14 @@
import com.google.gerrit.reviewdb.AccountGroupMember;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.ssh.SshInfo;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -75,4 +78,12 @@
// who have a lower interest in the change.
}
}
+
+ /** Get the groups which own the project. */
+ private Set<AccountGroup.Id> getProjectOwners() {
+ final ProjectState r;
+
+ r = FutureUtil.getOrNull(args.projectCache.get(change.getProject()));
+ return r != null ? r.getOwners() : Collections.<AccountGroup.Id> emptySet();
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
index f2ab9fa..196db4b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
@@ -17,8 +17,10 @@
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.StarredChangesCache;
import com.google.gerrit.server.IdentifiedUser.GenericFactory;
import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountProjectWatchCache;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.config.WildProjectName;
@@ -38,6 +40,8 @@
final ProjectCache projectCache;
final AccountCache accountCache;
final PatchListCache patchListCache;
+ final StarredChangesCache starredChangesCache;
+ final AccountProjectWatchCache accountProjectWatchCache;
final FromAddressGenerator fromAddressGenerator;
final EmailSender emailSender;
final PatchSetInfoFactory patchSetInfoFactory;
@@ -53,6 +57,8 @@
@Inject
EmailArguments(GitRepositoryManager server, ProjectCache projectCache,
AccountCache accountCache, PatchListCache patchListCache,
+ StarredChangesCache starredChangesCache,
+ AccountProjectWatchCache accountProjectWatchCache,
FromAddressGenerator fromAddressGenerator, EmailSender emailSender,
PatchSetInfoFactory patchSetInfoFactory,
GenericFactory identifiedUserFactory,
@@ -65,6 +71,8 @@
this.projectCache = projectCache;
this.accountCache = accountCache;
this.patchListCache = patchListCache;
+ this.starredChangesCache = starredChangesCache;
+ this.accountProjectWatchCache = accountProjectWatchCache;
this.fromAddressGenerator = fromAddressGenerator;
this.emailSender = emailSender;
this.patchSetInfoFactory = patchSetInfoFactory;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
index 10f6510..ce8c8f9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
@@ -19,29 +19,34 @@
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
-abstract class EmailHeader {
- abstract boolean isEmpty();
+public abstract class EmailHeader {
+ public abstract boolean isEmpty();
- abstract void write(Writer w) throws IOException;
+ public abstract void write(Writer w) throws IOException;
- static class String extends EmailHeader {
+ public static class String extends EmailHeader {
private java.lang.String value;
- String(java.lang.String v) {
+ public String(java.lang.String v) {
value = v;
}
+ public java.lang.String getString() {
+ return value;
+ }
+
@Override
- boolean isEmpty() {
+ public boolean isEmpty() {
return value == null || value.length() == 0;
}
@Override
- void write(Writer w) throws IOException {
+ public void write(Writer w) throws IOException {
if (needsQuotedPrintable(value)) {
w.write(quotedPrintable(value));
} else {
@@ -84,20 +89,24 @@
return r.toString();
}
- static class Date extends EmailHeader {
+ public static class Date extends EmailHeader {
private java.util.Date value;
- Date(java.util.Date v) {
+ public Date(java.util.Date v) {
value = v;
}
+ public java.util.Date getDate() {
+ return value;
+ }
+
@Override
- boolean isEmpty() {
+ public boolean isEmpty() {
return value == null;
}
@Override
- void write(Writer w) throws IOException {
+ public void write(Writer w) throws IOException {
final SimpleDateFormat fmt;
// Mon, 1 Jun 2009 10:49:44 -0700
fmt = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH);
@@ -105,17 +114,21 @@
}
}
- static class AddressList extends EmailHeader {
+ public static class AddressList extends EmailHeader {
private final List<Address> list = new ArrayList<Address>();
- AddressList() {
+ public AddressList() {
}
- AddressList(Address addr) {
+ public AddressList(Address addr) {
add(addr);
}
- void add(Address addr) {
+ public List<Address> getAddressList() {
+ return Collections.unmodifiableList(list);
+ }
+
+ public void add(Address addr) {
list.add(addr);
}
@@ -128,12 +141,12 @@
}
@Override
- boolean isEmpty() {
+ public boolean isEmpty() {
return list.isEmpty();
}
@Override
- void write(Writer w) throws IOException {
+ public void write(Writer w) throws IOException {
int len = 8;
boolean firstAddress = true;
boolean needComma = false;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
index 2d739ea..b1309a4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
@@ -19,6 +19,7 @@
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -87,7 +88,7 @@
@Override
public Address from(final Account.Id fromId) {
if (fromId != null) {
- final Account a = accountCache.get(fromId).getAccount();
+ Account a = FutureUtil.get(accountCache.getAccount(fromId));
if (a.getPreferredEmail() != null) {
return new Address(a.getFullName(), a.getPreferredEmail());
}
@@ -138,7 +139,7 @@
final String senderName;
if (fromId != null) {
- final Account account = accountCache.get(fromId).getAccount();
+ Account account = FutureUtil.get(accountCache.getAccount(fromId));
String fullName = account.getFullName();
if (fullName == null || "".equals(fullName)) {
fullName = "Anonymous Coward";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
index 913e352..587aeca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
@@ -16,6 +16,8 @@
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.UserIdentity;
+import com.google.gerrit.server.mail.EmailHeader.AddressList;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.mail.EmailHeader.AddressList;
@@ -40,7 +42,6 @@
import java.util.List;
import java.util.Map;
-
/** Sends an email to one or more interested parties. */
public abstract class OutgoingEmail {
private static final Logger log = LoggerFactory.getLogger(OutgoingEmail.class);
@@ -85,7 +86,8 @@
format();
if (shouldSendMessage()) {
if (fromId != null) {
- final Account fromUser = args.accountCache.get(fromId).getAccount();
+ final Account fromUser =
+ FutureUtil.get(args.accountCache.getAccount(fromId));
if (fromUser.getGeneralPreferences().isCopySelfOnEmails()) {
// If we are impersonating a user, make sure they receive a CC of
@@ -155,7 +157,8 @@
}
protected String getFromLine() {
- final Account account = args.accountCache.get(fromId).getAccount();
+ final Account account =
+ FutureUtil.get(args.accountCache.getAccount(fromId));
final String name = account.getFullName();
final String email = account.getPreferredEmail();
StringBuilder f = new StringBuilder();
@@ -232,7 +235,8 @@
return "Anonymous Coward";
}
- final Account userAccount = args.accountCache.get(accountId).getAccount();
+ Account userAccount =
+ FutureUtil.get(args.accountCache.getAccount(accountId));
String name = userAccount.getFullName();
if (name == null) {
name = userAccount.getPreferredEmail();
@@ -244,9 +248,9 @@
}
public String getNameEmailFor(Account.Id accountId) {
- AccountState who = args.accountCache.get(accountId);
- String name = who.getAccount().getFullName();
- String email = who.getAccount().getPreferredEmail();
+ Account who = FutureUtil.get(args.accountCache.getAccount(accountId));
+ String name = who.getFullName();
+ String email = who.getPreferredEmail();
if (name != null && email != null) {
return name + " <" + email + ">";
@@ -330,7 +334,7 @@
}
private Address toAddress(final Account.Id id) {
- final Account a = args.accountCache.get(id).getAccount();
+ final Account a = FutureUtil.get(args.accountCache.getAccount(id));
final String e = a.getPreferredEmail();
if (!a.isActive() || e == null) {
return null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
index d67d3b3..1a92321 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
@@ -17,6 +17,7 @@
import com.google.gerrit.common.Version;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -38,6 +39,13 @@
/** Sends email via a nearby SMTP server. */
@Singleton
public class SmtpEmailSender implements EmailSender {
+ public static class Module extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(EmailSender.class).to(SmtpEmailSender.class);
+ }
+ }
+
public static enum Encryption {
NONE, SSL, TLS;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
index a5121e9..cd39e1f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
@@ -15,39 +15,22 @@
package com.google.gerrit.server.patch;
-import static com.google.gerrit.server.ioutil.BasicSerialization.readBytes;
-import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32;
-import static com.google.gerrit.server.ioutil.BasicSerialization.writeBytes;
-import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
-import static org.eclipse.jgit.lib.ObjectIdSerialization.readCanBeNull;
-import static org.eclipse.jgit.lib.ObjectIdSerialization.readNotNull;
-import static org.eclipse.jgit.lib.ObjectIdSerialization.writeCanBeNull;
-import static org.eclipse.jgit.lib.ObjectIdSerialization.writeNotNull;
-
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gwtorm.client.Column;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import java.util.zip.DeflaterOutputStream;
-import java.util.zip.InflaterInputStream;
import javax.annotation.Nullable;
-public class PatchList implements Serializable {
- private static final long serialVersionUID = PatchListKey.serialVersionUID;
+public class PatchList {
private static final Comparator<PatchListEntry> PATCH_CMP =
new Comparator<PatchListEntry>() {
@Override
@@ -57,35 +40,56 @@
};
@Nullable
- private transient ObjectId oldId;
- private transient ObjectId newId;
- private transient boolean intralineDifference;
- private transient PatchListEntry[] patches;
+ @Column(id = 1)
+ protected String oldIdName;
+
+ @Column(id = 2)
+ protected String newIdName;
+
+ @Column(id = 3)
+ protected boolean intralineDifference;
+
+ @Column(id = 4)
+ protected List<PatchListEntry> patches;
+
+ private volatile ObjectId oldId;
+ private volatile ObjectId newId;
+
+ protected PatchList() {
+ }
PatchList(@Nullable final AnyObjectId oldId, final AnyObjectId newId,
final boolean intralineDifference, final PatchListEntry[] patches) {
this.oldId = oldId != null ? oldId.copy() : null;
+ oldIdName = oldId != null ? oldId.name() : null;
this.newId = newId.copy();
+ newIdName = newId.name();
this.intralineDifference = intralineDifference;
Arrays.sort(patches, PATCH_CMP);
- this.patches = patches;
+ this.patches = Arrays.asList(patches);
}
/** Old side tree or commit; null only if this is a combined diff. */
@Nullable
public ObjectId getOldId() {
+ if (oldId == null && oldIdName != null) {
+ oldId = ObjectId.fromString(oldIdName);
+ }
return oldId;
}
/** New side commit. */
public ObjectId getNewId() {
+ if (newId == null) {
+ newId = ObjectId.fromString(newIdName);
+ }
return newId;
}
/** Get a sorted, unmodifiable list of all files in this list. */
public List<PatchListEntry> getPatches() {
- return Collections.unmodifiableList(Arrays.asList(patches));
+ return Collections.unmodifiableList(patches);
}
/** @return true if this list was computed with intraline difference enabled. */
@@ -108,7 +112,7 @@
* how the cache is keyed versus how the database is keyed.
*/
public List<Patch> toPatchList(final PatchSet.Id setId) {
- final ArrayList<Patch> r = new ArrayList<Patch>(patches.length);
+ final ArrayList<Patch> r = new ArrayList<Patch>(patches.size());
for (final PatchListEntry e : patches) {
r.add(e.toPatch(setId));
}
@@ -118,15 +122,15 @@
/** Find an entry by name, returning an empty entry if not present. */
public PatchListEntry get(final String fileName) {
final int index = search(fileName);
- return 0 <= index ? patches[index] : PatchListEntry.empty(fileName);
+ return 0 <= index ? patches.get(index) : PatchListEntry.empty(fileName);
}
private int search(final String fileName) {
- int high = patches.length;
+ int high = patches.size();
int low = 0;
while (low < high) {
final int mid = (low + high) >>> 1;
- final int cmp = patches[mid].getNewName().compareTo(fileName);
+ final int cmp = patches.get(mid).getNewName().compareTo(fileName);
if (cmp < 0)
low = mid + 1;
else if (cmp == 0)
@@ -136,39 +140,4 @@
}
return -(low + 1);
}
-
- private void writeObject(final ObjectOutputStream output) throws IOException {
- final ByteArrayOutputStream buf = new ByteArrayOutputStream();
- final DeflaterOutputStream out = new DeflaterOutputStream(buf);
- try {
- writeCanBeNull(out, oldId);
- writeNotNull(out, newId);
- writeVarInt32(out, intralineDifference ? 1 : 0);
- writeVarInt32(out, patches.length);
- for (PatchListEntry p : patches) {
- p.writeTo(out);
- }
- } finally {
- out.close();
- }
- writeBytes(output, buf.toByteArray());
- }
-
- private void readObject(final ObjectInputStream input) throws IOException {
- final ByteArrayInputStream buf = new ByteArrayInputStream(readBytes(input));
- final InflaterInputStream in = new InflaterInputStream(buf);
- try {
- oldId = readCanBeNull(in);
- newId = readNotNull(in);
- intralineDifference = readVarInt32(in) != 0;
- final int cnt = readVarInt32(in);
- final PatchListEntry[] all = new PatchListEntry[cnt];
- for (int i = 0; i < all.length; i++) {
- all[i] = PatchListEntry.readFrom(in);
- }
- patches = all;
- } finally {
- in.close();
- }
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
index 1a6d2ae..664bc02 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCache.java
@@ -14,15 +14,17 @@
package com.google.gerrit.server.patch;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
/** Provides a cached list of {@link PatchListEntry}. */
public interface PatchListCache {
- public PatchList get(PatchListKey key);
+ public ListenableFuture<PatchList> get(PatchListKey key);
- public PatchList get(Change change, PatchSet patchSet);
+ public ListenableFuture<PatchList> get(Change change, PatchSet patchSet);
- public PatchList get(Change change, PatchSet patchSet, Whitespace whitespace);
+ public ListenableFuture<PatchList> get(Change change, PatchSet patchSet,
+ Whitespace whitespace);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
index eb62fe1..4f52e2a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -61,6 +61,9 @@
package com.google.gerrit.server.patch;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.prettify.common.BaseEdit;
+import com.google.gerrit.prettify.common.LineEdit;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.Project;
@@ -86,15 +89,12 @@
import org.eclipse.jgit.diff.RawTextIgnoreTrailingWhitespace;
import org.eclipse.jgit.diff.RawTextIgnoreWhitespaceChange;
import org.eclipse.jgit.diff.RenameDetector;
-import org.eclipse.jgit.diff.ReplaceEdit;
-import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.patch.FileHeader;
import org.eclipse.jgit.patch.FileHeader.PatchType;
@@ -127,8 +127,9 @@
protected void configure() {
final TypeLiteral<Cache<PatchListKey, PatchList>> type =
new TypeLiteral<Cache<PatchListKey, PatchList>>() {};
- disk(type, CACHE_NAME) //
+ cache(type, CACHE_NAME) //
.memoryLimit(128) // very large items, cache only a few
+ .diskLimit(16384) // cache a lot on disk, they are hard to make
.evictionPolicy(EvictionPolicy.LRU) // prefer most recent
.populateWith(Loader.class) //
;
@@ -146,16 +147,17 @@
cache = thecache;
}
- public PatchList get(final PatchListKey key) {
+ public ListenableFuture<PatchList> get(final PatchListKey key) {
return cache.get(key);
}
- public PatchList get(final Change change, final PatchSet patchSet) {
+ public ListenableFuture<PatchList> get(final Change change,
+ final PatchSet patchSet) {
return get(change, patchSet, Whitespace.IGNORE_NONE);
}
- public PatchList get(final Change change, final PatchSet patchSet,
- final Whitespace whitespace) {
+ public ListenableFuture<PatchList> get(final Change change,
+ final PatchSet patchSet, final Whitespace whitespace) {
final Project.NameKey projectKey = change.getProject();
final ObjectId a = null;
final ObjectId b = ObjectId.fromString(patchSet.getRevision().get());
@@ -234,24 +236,43 @@
return new PatchList(a, b, computeIntraline, entries);
}
+ private static List<LineEdit> editsToLineEdits(List<Edit> edits) {
+ List<LineEdit> l = new ArrayList<LineEdit>(edits.size());
+ for (Edit e : edits) {
+ l.add(new LineEdit(e));
+ }
+ return l;
+ }
+
+ private static List<BaseEdit> editsToBaseEdits(List<Edit> edits) {
+ List<BaseEdit> l = new ArrayList<BaseEdit>(edits.size());
+ for (Edit e : edits) {
+ l.add(new BaseEdit(e));
+ }
+ return l;
+ }
+
private PatchListEntry newEntry(Repository repo, RevTree aTree,
RevTree bTree, FileHeader fileHeader) throws IOException {
final FileMode oldMode = fileHeader.getOldMode();
final FileMode newMode = fileHeader.getNewMode();
if (oldMode == FileMode.GITLINK || newMode == FileMode.GITLINK) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
+ return new PatchListEntry(fileHeader, Collections
+ .<LineEdit> emptyList());
}
if (aTree == null // want combined diff
|| fileHeader.getPatchType() != PatchType.UNIFIED
|| fileHeader.getHunks().isEmpty()) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
+ return new PatchListEntry(fileHeader, Collections
+ .<LineEdit> emptyList());
}
- List<Edit> edits = fileHeader.toEditList();
+ List<LineEdit> edits = editsToLineEdits(fileHeader.toEditList());
if (edits.isEmpty()) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList());
+ return new PatchListEntry(fileHeader, Collections
+ .<LineEdit> emptyList());
}
if (!computeIntraline) {
return new PatchListEntry(fileHeader, edits);
@@ -267,11 +288,11 @@
Text bContent = null;
for (int i = 0; i < edits.size(); i++) {
- Edit e = edits.get(i);
+ LineEdit e = edits.get(i);
if (e.getType() == Edit.Type.REPLACE) {
if (aContent == null) {
- edits = new ArrayList<Edit>(edits);
+ edits = new ArrayList<LineEdit>(edits);
aContent = read(repo, fileHeader.getOldPath(), aTree);
bContent = read(repo, fileHeader.getNewPath(), bTree);
combineLineEdits(edits, aContent, bContent);
@@ -282,15 +303,16 @@
CharText a = new CharText(aContent, e.getBeginA(), e.getEndA());
CharText b = new CharText(bContent, e.getBeginB(), e.getEndB());
- List<Edit> wordEdits = new MyersDiff(a, b).getEdits();
+ List<BaseEdit> wordEdits =
+ editsToBaseEdits(new MyersDiff(a, b).getEdits());
// Combine edits that are really close together. If they are
// just a few characters apart we tend to get better results
// by joining them together and taking the whole span.
//
for (int j = 0; j < wordEdits.size() - 1;) {
- Edit c = wordEdits.get(j);
- Edit n = wordEdits.get(j + 1);
+ BaseEdit c = wordEdits.get(j);
+ BaseEdit n = wordEdits.get(j + 1);
if (n.getBeginA() - c.getEndA() <= 5
|| n.getBeginB() - c.getEndB() <= 5) {
@@ -302,7 +324,7 @@
if (canCoalesce(a, c.getEndA(), n.getBeginA())
&& canCoalesce(b, c.getEndB(), n.getBeginB())) {
- wordEdits.set(j, new Edit(ab, ae, bb, be));
+ wordEdits.set(j, new LineEdit(ab, ae, bb, be));
wordEdits.remove(j + 1);
continue;
}
@@ -316,7 +338,7 @@
// to produce some crazy stuff.
//
for (int j = 0; j < wordEdits.size(); j++) {
- Edit c = wordEdits.get(j);
+ BaseEdit c = wordEdits.get(j);
int ab = c.getBeginA();
int ae = c.getEndA();
@@ -331,7 +353,7 @@
// with silly stuff like "es" -> "es = Addresses".
//
if (1 < j) {
- Edit p = wordEdits.get(j - 1);
+ BaseEdit p = wordEdits.get(j - 1);
if (p.getEndA() == ab || p.getEndB() == bb) {
if (p.getEndA() == ab && p.getBeginA() < p.getEndA()) {
ab = p.getBeginA();
@@ -448,20 +470,20 @@
be++;
}
- wordEdits.set(j, new Edit(ab, ae, bb, be));
+ wordEdits.set(j, new BaseEdit(ab, ae, bb, be));
}
- edits.set(i, new ReplaceEdit(e, wordEdits));
+ edits.set(i, new LineEdit(e, wordEdits));
}
}
return new PatchListEntry(fileHeader, edits);
}
- private static void combineLineEdits(List<Edit> edits, Text a, Text b) {
+ private static void combineLineEdits(List<LineEdit> edits, Text a, Text b) {
for (int j = 0; j < edits.size() - 1;) {
- Edit c = edits.get(j);
- Edit n = edits.get(j + 1);
+ BaseEdit c = edits.get(j);
+ BaseEdit n = edits.get(j + 1);
// Combine edits that are really close together. Right now our rule
// is, coalesce two line edits which are only one line apart if that
@@ -480,7 +502,7 @@
int bb = c.getBeginB();
int be = n.getEndB();
- edits.set(j, new Edit(ab, ae, bb, be));
+ edits.set(j, new LineEdit(ab, ae, bb, be));
edits.remove(j + 1);
continue;
}
@@ -512,7 +534,7 @@
return true;
}
- private static int findLF(List<Edit> edits, int j, CharText t, int b) {
+ private static int findLF(List<BaseEdit> edits, int j, CharText t, int b) {
int lf = b;
int limit = 0 < j ? edits.get(j - 1).getEndB() : 0;
while (limit < lf && t.charAt(lf) != '\n') {
@@ -536,13 +558,7 @@
if (tw == null || tw.getFileMode(0).getObjectType() != Constants.OBJ_BLOB) {
return Text.EMPTY;
}
- ObjectLoader ldr;
- try {
- ldr = repo.open(tw.getObjectId(0), Constants.OBJ_BLOB);
- } catch (MissingObjectException notFound) {
- return Text.EMPTY;
- }
- return new Text(ldr.getCachedBytes());
+ return new Text(repo.open(tw.getObjectId(0), Constants.OBJ_BLOB));
}
private static AnyObjectId aFor(final PatchListKey key,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
index c4484fc..88d5b68 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
@@ -23,13 +23,14 @@
import static com.google.gerrit.server.ioutil.BasicSerialization.writeString;
import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
+import com.google.gerrit.prettify.common.BaseEdit;
+import com.google.gerrit.prettify.common.LineEdit;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.Patch.ChangeType;
import com.google.gerrit.reviewdb.Patch.PatchType;
+import com.google.gwtorm.client.Column;
-import org.eclipse.jgit.diff.Edit;
-import org.eclipse.jgit.diff.ReplaceEdit;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.patch.CombinedFileHeader;
@@ -50,20 +51,40 @@
static PatchListEntry empty(final String fileName) {
return new PatchListEntry(ChangeType.MODIFIED, PatchType.UNIFIED, null,
- fileName, EMPTY_HEADER, Collections.<Edit> emptyList());
+ fileName, EMPTY_HEADER, Collections.<LineEdit> emptyList());
}
- private final ChangeType changeType;
- private final PatchType patchType;
- private final String oldName;
- private final String newName;
- private final byte[] header;
- private final List<Edit> edits;
+ @Column(id = 1)
+ protected char changeTypeCode;
- PatchListEntry(final FileHeader hdr, List<Edit> editList) {
+ @Column(id = 2)
+ protected char patchTypeCode;
+
+ @Column(id = 3)
+ protected String oldName;
+
+ @Column(id = 4)
+ protected String newName;
+
+ @Column(id = 5)
+ protected byte[] header;
+
+ @Column(id = 6)
+ protected List<LineEdit> edits;
+
+ protected ChangeType changeType;
+ protected PatchType patchType;
+
+ protected PatchListEntry(){
+ }
+
+ PatchListEntry(final FileHeader hdr, List<LineEdit> editList) {
changeType = toChangeType(hdr);
patchType = toPatchType(hdr);
+ changeTypeCode = changeType.getCode();
+ patchTypeCode = patchType.getCode();
+
switch (changeType) {
case DELETED:
oldName = null;
@@ -94,15 +115,17 @@
|| hdr.getNewMode() == FileMode.GITLINK) {
edits = Collections.emptyList();
} else {
- edits = Collections.unmodifiableList(editList);
+ edits = editList;
}
}
private PatchListEntry(final ChangeType changeType,
final PatchType patchType, final String oldName, final String newName,
- final byte[] header, final List<Edit> edits) {
+ final byte[] header, final List<LineEdit> edits) {
this.changeType = changeType;
this.patchType = patchType;
+ changeTypeCode = changeType.getCode();
+ patchTypeCode = patchType.getCode();
this.oldName = oldName;
this.newName = newName;
this.header = header;
@@ -110,10 +133,16 @@
}
public ChangeType getChangeType() {
+ if (changeType == null) {
+ changeType = ChangeType.forCode(changeTypeCode);
+ }
return changeType;
}
public PatchType getPatchType() {
+ if (patchType == null) {
+ patchType = PatchType.forCode(patchTypeCode);
+ }
return patchType;
}
@@ -125,7 +154,7 @@
return newName;
}
- public List<Edit> getEdits() {
+ public List<LineEdit> getEdits() {
return edits;
}
@@ -149,20 +178,20 @@
}
void writeTo(final OutputStream out) throws IOException {
- writeEnum(out, changeType);
- writeEnum(out, patchType);
+ writeEnum(out, getChangeType());
+ writeEnum(out, getPatchType());
writeString(out, oldName);
writeString(out, newName);
writeBytes(out, header);
writeVarInt32(out, edits.size());
- for (final Edit e : edits) {
+ for (final LineEdit e : edits) {
write(out, e);
- if (e instanceof ReplaceEdit) {
- ReplaceEdit r = (ReplaceEdit) e;
- writeVarInt32(out, r.getInternalEdits().size());
- for (Edit i : r.getInternalEdits()) {
+ if (e.getEdits() != null) {
+ List<BaseEdit> intlEdits = e.getEdits();
+ writeVarInt32(out, intlEdits.size());
+ for (BaseEdit i : intlEdits) {
write(out, i);
}
} else {
@@ -171,7 +200,7 @@
}
}
- private void write(final OutputStream out, final Edit e) throws IOException {
+ private void write(final OutputStream out, final BaseEdit e) throws IOException {
writeVarInt32(out, e.getBeginA());
writeVarInt32(out, e.getEndA());
writeVarInt32(out, e.getBeginB());
@@ -186,34 +215,34 @@
final byte[] hdr = readBytes(in);
final int editCount = readVarInt32(in);
- final Edit[] editArray = new Edit[editCount];
+ final LineEdit[] editArray = new LineEdit[editCount];
for (int i = 0; i < editCount; i++) {
- editArray[i] = readEdit(in);
+ BaseEdit self = readEdit(in);
int innerCount = readVarInt32(in);
- if (0 < innerCount) {
- Edit[] inner = new Edit[innerCount];
+ if (innerCount == 0) {
+ editArray[i] = new LineEdit(self, null);
+ } else {
+ BaseEdit[] inner = new BaseEdit[innerCount];
for (int innerIdx = 0; innerIdx < innerCount; innerIdx++) {
inner[innerIdx] = readEdit(in);
}
- editArray[i] = new ReplaceEdit(editArray[i], toList(inner));
+ editArray[i] =
+ new LineEdit(self, Collections.unmodifiableList(Arrays
+ .asList((inner))));
}
}
return new PatchListEntry(changeType, patchType, oldName, newName, hdr,
- toList(editArray));
+ Collections.unmodifiableList(Arrays.asList(editArray)));
}
- private static List<Edit> toList(Edit[] l) {
- return Collections.unmodifiableList(Arrays.asList(l));
- }
-
- private static Edit readEdit(final InputStream in) throws IOException {
+ private static BaseEdit readEdit(final InputStream in) throws IOException {
final int beginA = readVarInt32(in);
final int endA = readVarInt32(in);
final int beginB = readVarInt32(in);
final int endB = readVarInt32(in);
- return new Edit(beginA, endA, beginB, endB);
+ return new BaseEdit(beginA, endA, beginB, endB);
}
private static byte[] compact(final FileHeader h) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
index 26ee17b..e9d35bb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
@@ -14,55 +14,66 @@
package com.google.gerrit.server.patch;
-import static com.google.gerrit.server.ioutil.BasicSerialization.readEnum;
-import static com.google.gerrit.server.ioutil.BasicSerialization.writeEnum;
-import static org.eclipse.jgit.lib.ObjectIdSerialization.readCanBeNull;
-import static org.eclipse.jgit.lib.ObjectIdSerialization.readNotNull;
-import static org.eclipse.jgit.lib.ObjectIdSerialization.writeCanBeNull;
-import static org.eclipse.jgit.lib.ObjectIdSerialization.writeNotNull;
-
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.AccountDiffPreference.Whitespace;
+import com.google.gwtorm.client.Column;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-
import javax.annotation.Nullable;
-public class PatchListKey implements Serializable {
- static final long serialVersionUID = 13L;
+public class PatchListKey {
+ @Column(id = 1)
+ protected String oldIdName;
- private transient ObjectId oldId;
- private transient ObjectId newId;
- private transient Whitespace whitespace;
+ @Column(id = 2)
+ protected String newIdName;
+
+ @Column(id = 3)
+ protected char whitespaceCode;
+
+ private volatile ObjectId oldId;
+ private volatile ObjectId newId;
+ private volatile Whitespace whitespace;
transient Project.NameKey projectKey; // not required to form the key
+ protected PatchListKey() {
+ }
+
public PatchListKey(final Project.NameKey pk, final AnyObjectId a,
final AnyObjectId b, final Whitespace ws) {
projectKey = pk;
oldId = a != null ? a.copy() : null;
newId = b.copy();
+ oldIdName = oldId != null ? oldId.name() : null;
+ newIdName = newId.name();
whitespace = ws;
+ whitespaceCode = ws.getCode();
}
/** Old side commit, or null to assume ancestor or combined merge. */
@Nullable
public ObjectId getOldId() {
+ if (oldId == null && oldIdName != null) {
+ oldId = ObjectId.fromString(oldIdName);
+ }
return oldId;
}
/** New side commit name. */
public ObjectId getNewId() {
+ if (newId == null) {
+ newId = ObjectId.fromString(newIdName);
+ }
return newId;
}
public Whitespace getWhitespace() {
+ if (whitespace == null) {
+ whitespace = Whitespace.forCode(whitespaceCode);
+ }
return whitespace;
}
@@ -70,12 +81,12 @@
public int hashCode() {
int h = 0;
- if (oldId != null) {
- h = h * 31 + oldId.hashCode();
+ if (oldIdName != null) {
+ h = h * 31 + getOldId().hashCode();
}
- h = h * 31 + newId.hashCode();
- h = h * 31 + whitespace.name().hashCode();
+ h = h * 31 + getNewId().hashCode();
+ h = h * 31 + whitespaceCode;
return h;
}
@@ -84,9 +95,10 @@
public boolean equals(final Object o) {
if (o instanceof PatchListKey) {
final PatchListKey k = (PatchListKey) o;
- return eq(oldId, k.oldId) //
- && eq(newId, k.newId) //
- && whitespace == k.whitespace;
+ return oldIdName != null ? oldIdName.equals(k.oldIdName)
+ : k.oldIdName == null //
+ && newIdName.equals(k.newIdName) //
+ && getWhitespace() == k.getWhitespace();
}
return false;
}
@@ -99,31 +111,12 @@
n.append(projectKey.get());
n.append(" ");
}
- n.append(oldId != null ? oldId.name() : "BASE");
+ n.append(oldIdName != null ? oldIdName : "BASE");
n.append("..");
- n.append(newId.name());
+ n.append(newIdName);
n.append(" ");
- n.append(whitespace.name());
+ n.append(getWhitespace().name());
n.append("]");
return n.toString();
}
-
- private static boolean eq(final ObjectId a, final ObjectId b) {
- if (a == null && b == null) {
- return true;
- }
- return a != null && b != null && AnyObjectId.equals(a, b);
- }
-
- private void writeObject(final ObjectOutputStream out) throws IOException {
- writeCanBeNull(out, oldId);
- writeNotNull(out, newId);
- writeEnum(out, whitespace);
- }
-
- private void readObject(final ObjectInputStream in) throws IOException {
- oldId = readCanBeNull(in);
- newId = readNotNull(in);
- whitespace = readEnum(in, Whitespace.values());
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
index 9617172..c111ba7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
@@ -14,6 +14,9 @@
package com.google.gerrit.server.patch;
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
@@ -21,8 +24,9 @@
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.UserIdentity;
-import com.google.gerrit.server.account.AccountByEmailCache;
+import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
@@ -37,6 +41,7 @@
import java.io.IOException;
import java.sql.Timestamp;
import java.util.Set;
+import java.util.concurrent.Future;
/**
@@ -46,72 +51,80 @@
public class PatchSetInfoFactory {
private final GitRepositoryManager repoManager;
private final SchemaFactory<ReviewDb> schemaFactory;
- private final AccountByEmailCache byEmailCache;
+ private final AccountCache accountCache;
@Inject
public PatchSetInfoFactory(final GitRepositoryManager grm,
final SchemaFactory<ReviewDb> schemaFactory,
- final AccountByEmailCache byEmailCache) {
+ final AccountCache accountCache) {
this.repoManager = grm;
this.schemaFactory = schemaFactory;
- this.byEmailCache = byEmailCache;
+ this.accountCache = accountCache;
}
public PatchSetInfo get(RevCommit src, PatchSet.Id psi) {
+ Future<UserIdentity> author = toUserIdentity(src.getAuthorIdent());
+ Future<UserIdentity> committer = toUserIdentity(src.getCommitterIdent());
+
PatchSetInfo info = new PatchSetInfo(psi);
info.setSubject(src.getShortMessage());
info.setMessage(src.getFullMessage());
- info.setAuthor(toUserIdentity(src.getAuthorIdent()));
- info.setCommitter(toUserIdentity(src.getCommitterIdent()));
-
+ info.setAuthor(FutureUtil.get(author));
+ info.setCommitter(FutureUtil.get(committer));
return info;
}
public PatchSetInfo get(PatchSet.Id patchSetId)
throws PatchSetInfoNotAvailableException {
- ReviewDb db = null;
- Repository repo = null;
+ final RevCommit src;
+
try {
- db = schemaFactory.open();
- final PatchSet patchSet = db.patchSets().get(patchSetId);
- final Change change = db.changes().get(patchSet.getId().getParentKey());
- final Project.NameKey projectKey = change.getProject();
- final String projectName = projectKey.get();
- repo = repoManager.openRepository(projectName);
- final RevWalk rw = new RevWalk(repo);
- final RevCommit src =
- rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
- return get(src, patchSetId);
- } catch (OrmException e) {
- throw new PatchSetInfoNotAvailableException(e);
- } catch (IOException e) {
- throw new PatchSetInfoNotAvailableException(e);
- } finally {
- if (db != null) {
+ final ReviewDb db = schemaFactory.open();
+ try {
+ final PatchSet patchSet = db.patchSets().get(patchSetId);
+ final String revId = patchSet.getRevision().get();
+ final Change change = db.changes().get(patchSet.getId().getParentKey());
+ final Project.NameKey projectKey = change.getProject();
+ try {
+ final Repository repo = repoManager.openRepository(projectKey.get());
+ try {
+ final RevWalk rw = new RevWalk(repo);
+ src = rw.parseCommit(ObjectId.fromString(revId));
+ } finally {
+ repo.close();
+ }
+ } catch (IOException e) {
+ throw new PatchSetInfoNotAvailableException(e);
+ }
+ } finally {
db.close();
}
- if (repo != null) {
- repo.close();
- }
- }
- }
-
- private UserIdentity toUserIdentity(final PersonIdent who) {
- final UserIdentity u = new UserIdentity();
- u.setName(who.getName());
- u.setEmail(who.getEmailAddress());
- u.setDate(new Timestamp(who.getWhen().getTime()));
- u.setTimeZone(who.getTimeZoneOffset());
-
- // If only one account has access to this email address, select it
- // as the identity of the user.
- //
- final Set<Account.Id> a = byEmailCache.get(u.getEmail());
- if (a.size() == 1) {
- u.setAccount(a.iterator().next());
+ } catch (OrmException e) {
+ throw new PatchSetInfoNotAvailableException(e);
}
- return u;
+ return get(src, patchSetId);
}
+ private ListenableFuture<UserIdentity> toUserIdentity(final PersonIdent who) {
+ return Futures.compose(accountCache.byEmail(who.getEmailAddress()),
+ new Function<Set<Account.Id>, UserIdentity>() {
+ @Override
+ public UserIdentity apply(Set<Account.Id> from) {
+ final UserIdentity u = new UserIdentity();
+ u.setName(who.getName());
+ u.setEmail(who.getEmailAddress());
+ u.setDate(new Timestamp(who.getWhen().getTime()));
+ u.setTimeZone(who.getTimeZoneOffset());
+
+ if (from != null && from.size() == 1) {
+ // If only one account has access to this email address, select it
+ // as the identity of the user.
+ //
+ u.setAccount(from.iterator().next());
+ }
+ return u;
+ }
+ });
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PublishComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PublishComments.java
index f3e2890..d1f883c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PublishComments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PublishComments.java
@@ -274,7 +274,7 @@
}
private List<PatchLineComment> drafts() throws OrmException {
- return db.patchComments().draft(patchSetId, user.getAccountId()).toList();
+ return db.patchComments().draftByPatchSet(patchSetId, user.getAccountId()).toList();
}
private void email() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
index 35b5ee5..748d87b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.project;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.reviewdb.Project;
/** Cache of project information, including access rights. */
@@ -24,11 +25,11 @@
* @param projectName name of the project.
* @return the cached data; null if no such project exists.
*/
- public ProjectState get(Project.NameKey projectName);
+ public ListenableFuture<ProjectState> get(Project.NameKey projectName);
/** Invalidate the cached information about the given project. */
- public void evict(Project p);
+ public ListenableFuture<Void> evictAsync(Project p);
/** Invalidate the cached information about all projects. */
- public void evictAll();
+ public ListenableFuture<Void> evictAllAsync();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index 48eef87..2099432 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.project;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
@@ -23,6 +25,7 @@
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Module;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
@@ -41,7 +44,7 @@
protected void configure() {
final TypeLiteral<Cache<Project.NameKey, ProjectState>> type =
new TypeLiteral<Cache<Project.NameKey, ProjectState>>() {};
- core(type, CACHE_NAME).populateWith(Loader.class);
+ cache(type, CACHE_NAME).populateWith(Loader.class);
bind(ProjectCacheImpl.class);
bind(ProjectCache.class).to(ProjectCacheImpl.class);
}
@@ -62,29 +65,30 @@
* @param projectName name of the project.
* @return the cached data; null if no such project exists.
*/
- public ProjectState get(final Project.NameKey projectName) {
+ public ListenableFuture<ProjectState> get(Project.NameKey projectName) {
return byName.get(projectName);
}
/** Invalidate the cached information about the given project. */
- public void evict(final Project p) {
+ public ListenableFuture<Void> evictAsync(final Project p) {
if (p != null) {
- byName.remove(p.getNameKey());
+ return byName.removeAsync(p.getNameKey());
}
+ return Futures.immediateFuture(null);
}
/** Invalidate the cached information about all projects. */
- public void evictAll() {
- byName.removeAll();
+ public ListenableFuture<Void> evictAllAsync() {
+ return byName.removeAllAsync();
}
static class Loader extends EntryCreator<Project.NameKey, ProjectState> {
- private final ProjectState.Factory projectStateFactory;
+ private final Provider<ProjectState> projectStateProvider;
private final SchemaFactory<ReviewDb> schema;
@Inject
- Loader(ProjectState.Factory psf, SchemaFactory<ReviewDb> sf) {
- projectStateFactory = psf;
+ Loader(Provider<ProjectState> psp, SchemaFactory<ReviewDb> sf) {
+ projectStateProvider = psp;
schema = sf;
}
@@ -101,7 +105,7 @@
Collections.unmodifiableCollection(db.refRights().byProject(
p.getNameKey()).toList());
- return projectStateFactory.create(p, rights);
+ return projectStateProvider.get().init(p, rights);
} finally {
db.close();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index 67f543b..502baf7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -22,6 +22,7 @@
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.ReplicationUser;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -43,7 +44,7 @@
public ProjectControl controlFor(Project.NameKey nameKey, CurrentUser user)
throws NoSuchProjectException {
- final ProjectState p = projectCache.get(nameKey);
+ final ProjectState p = FutureUtil.getOrNull(projectCache.get(nameKey));
if (p == null) {
throw new NoSuchProjectException(nameKey);
}
@@ -63,7 +64,7 @@
public ProjectControl controlFor(final Project.NameKey nameKey)
throws NoSuchProjectException {
- final ProjectState p = projectCache.get(nameKey);
+ final ProjectState p = FutureUtil.getOrNull(projectCache.get(nameKey));
if (p == null) {
throw new NoSuchProjectException(nameKey);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 00e92b6..09a345f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -21,8 +21,9 @@
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.WildProjectName;
+import com.google.gerrit.server.util.FutureUtil;
+import com.google.gwtorm.client.Column;
import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
import java.util.ArrayList;
import java.util.Collection;
@@ -33,30 +34,31 @@
/** Cached information on a project. */
public class ProjectState {
- public interface Factory {
- ProjectState create(Project project, Collection<RefRight> localRights);
- }
-
private final AnonymousUser anonymousUser;
private final Project.NameKey wildProject;
private final ProjectCache projectCache;
- private final Project project;
- private final Collection<RefRight> localRights;
- private final Set<AccountGroup.Id> owners;
+ @Column(id = 1)
+ protected Project project;
+
+ @Column(id = 2)
+ protected Collection<RefRight> localRights;
+
+ @Column(id = 3)
+ protected Set<AccountGroup.Id> owners;
private volatile Collection<RefRight> inheritedRights;
@Inject
protected ProjectState(final AnonymousUser anonymousUser,
final ProjectCache projectCache,
- @WildProjectName final Project.NameKey wildProject,
- @Assisted final Project project,
- @Assisted final Collection<RefRight> rights) {
+ @WildProjectName final Project.NameKey wildProject) {
this.anonymousUser = anonymousUser;
this.projectCache = projectCache;
this.wildProject = wildProject;
+ }
+ protected ProjectState init(final Project project, final Collection<RefRight> rights) {
this.project = project;
this.localRights = rights;
@@ -68,6 +70,7 @@
}
}
owners = Collections.unmodifiableSet(groups);
+ return this;
}
public Project getProject() {
@@ -107,7 +110,7 @@
Project.NameKey parent = project.getParent();
while (parent != null && seen.add(parent)) {
- ProjectState s = projectCache.get(parent);
+ ProjectState s = FutureUtil.get(projectCache.get(parent));
if (s != null) {
inherited.addAll(s.getLocalRights());
parent = s.getProject().getParent();
@@ -125,7 +128,7 @@
}
private Collection<RefRight> getWildProjectRights() {
- final ProjectState s = projectCache.get(wildProject);
+ ProjectState s = FutureUtil.get(projectCache.get(wildProject));
return s != null ? s.getLocalRights() : Collections.<RefRight> emptyList();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
index 18a0c82..69344a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AgePredicate.java
@@ -21,10 +21,14 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Provider;
-class AgePredicate extends OperatorPredicate<ChangeData> {
+import java.util.EnumSet;
+
+class AgePredicate extends OperatorPredicate<ChangeData> implements
+ Prefetchable {
private final Provider<ReviewDb> dbProvider;
private final long cut;
@@ -51,4 +55,9 @@
public int getCost() {
return 1;
}
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ return EnumSet.of(NeededData.CHANGE);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
index e74b390..3af9061 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
@@ -14,19 +14,32 @@
package com.google.gerrit.server.query.change;
-import com.google.gerrit.server.query.AndPredicate;
+import com.google.common.collect.Maps;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.ResultSet;
import com.google.gwtorm.client.impl.ListResultSet;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.EnumSet;
import java.util.List;
+import java.util.Map;
-class AndSource extends AndPredicate<ChangeData> implements ChangeDataSource {
+class AndSource extends PrefetchableAndPredicate implements ChangeDataSource {
+ interface Factory {
+ AndSource create(Collection<? extends Predicate<ChangeData>> that);
+ }
+
private static final Comparator<Predicate<ChangeData>> CMP =
new Comparator<Predicate<ChangeData>>() {
@Override
@@ -68,9 +81,15 @@
}
private int cardinality = -1;
+ private final Provider<ReviewDb> dbProvider;
+ private final ProjectCache projectCache;
- AndSource(final Collection<? extends Predicate<ChangeData>> that) {
+ @Inject
+ AndSource(Provider<ReviewDb> dbProvider, ProjectCache projectCache,
+ @Assisted final Collection<? extends Predicate<ChangeData>> that) {
super(sort(that));
+ this.dbProvider = dbProvider;
+ this.projectCache = projectCache;
}
@Override
@@ -91,17 +110,17 @@
ArrayList<ChangeData> r = new ArrayList<ChangeData>();
ChangeData last = null;
boolean skipped = false;
- for (ChangeData data : source.read()) {
- if (match(data)) {
- r.add(data);
+ for (ChangeData cd : prefetchData(source.read())) {
+ if (match(cd)) {
+ r.add(cd);
} else {
skipped = true;
}
- last = data;
+ last = cd;
}
if (skipped && last != null && source instanceof Paginated) {
- // If we our source is a paginated source and we skipped at
+ // If our source is a paginated source and we skipped at
// least one of its results, we may not have filled the full
// limit the caller wants. Restart the source and continue.
//
@@ -110,13 +129,13 @@
ChangeData lastBeforeRestart = last;
skipped = false;
last = null;
- for (ChangeData data : p.restart(lastBeforeRestart)) {
- if (match(data)) {
- r.add(data);
+ for (ChangeData cd : prefetchData(p.restart(lastBeforeRestart))) {
+ if (match(cd)) {
+ r.add(cd);
} else {
skipped = true;
}
- last = data;
+ last = cd;
}
}
}
@@ -133,6 +152,37 @@
return null;
}
+ private Collection<ChangeData> prefetchData(ResultSet<ChangeData> resultSet) throws OrmException {
+ final List<ChangeData> data = resultSet.toList();
+ final EnumSet<NeededData> needed = getNeededData();
+
+ if (needed.contains(NeededData.PROJECT_STATE)) {
+ needed.add(NeededData.CHANGE);
+ }
+
+ if (needed.contains(NeededData.CHANGE)) {
+ Map<Change.Id, ChangeData> need = Maps.newHashMap();
+ for (ChangeData cd : data) {
+ if (!cd.hasChange()) {
+ need.put(cd.getId(), cd);
+ }
+ }
+ if (!need.isEmpty()) {
+ for (Change c : dbProvider.get().changes().get(need.keySet())) {
+ need.get(c.getId()).setChange(c);
+ }
+ }
+ }
+
+ if (needed.contains(NeededData.PROJECT_STATE)) {
+ for (ChangeData cd : data) {
+ cd.initProjectState(dbProvider, projectCache);
+ }
+ }
+
+ return data;
+ }
+
@Override
public int getCardinality() {
if (cardinality < 0) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java
index ae48fdc..7c3a9d3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BranchPredicate.java
@@ -18,10 +18,14 @@
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Provider;
-class BranchPredicate extends OperatorPredicate<ChangeData> {
+import java.util.EnumSet;
+
+class BranchPredicate extends OperatorPredicate<ChangeData> implements
+ Prefetchable {
private final Provider<ReviewDb> dbProvider;
private final String shortName;
@@ -44,4 +48,9 @@
public int getCost() {
return 1;
}
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ return EnumSet.of(NeededData.CHANGE);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 479a5ef..7ef137d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -24,6 +24,9 @@
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Provider;
@@ -31,8 +34,13 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.Future;
public class ChangeData {
+ public enum NeededData {
+ CHANGE, PATCHES, APPROVALS, COMMENTS, TRACKING_IDS, PROJECT_STATE
+ }
+
private final Change.Id legacyId;
private Change change;
private Collection<PatchSet> patches;
@@ -42,6 +50,7 @@
private Collection<PatchLineComment> comments;
private Collection<TrackingId> trackingIds;
private CurrentUser visibleTo;
+ private Future<ProjectState> projectState;
public ChangeData(final Change.Id id) {
legacyId = id;
@@ -68,7 +77,7 @@
return null;
}
- PatchList p = cache.get(c, ps);
+ PatchList p = FutureUtil.get(cache.get(c, ps));
List<String> r = new ArrayList<String>(p.getPatches().size());
for (PatchListEntry e : p.getPatches()) {
switch (e.getChangeType()) {
@@ -186,4 +195,37 @@
}
return trackingIds;
}
+
+ public ProjectState projectState(Provider<ReviewDb> db,
+ ProjectCache projectCache) throws OrmException {
+ initProjectState(db, projectCache);
+ return FutureUtil.get(projectState);
+ }
+
+ void initProjectState(Provider<ReviewDb> db, ProjectCache projectCache)
+ throws OrmException {
+ if (projectState == null) {
+ projectState = projectCache.get(change(db).getProject());
+ }
+ }
+
+ void setChange(Change change) {
+ this.change = change;
+ }
+
+ void setPatches(Collection<PatchSet> patches) {
+ this.patches = patches;
+ }
+
+ void setApprovals(Collection<PatchSetApproval> approvals) {
+ this.approvals = approvals;
+ }
+
+ void setComments(Collection<PatchLineComment> comments) {
+ this.comments = comments;
+ }
+
+ void setTrackingIds(Collection<TrackingId> trackingIds) {
+ this.trackingIds = trackingIds;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
index 83107bb..2cd3e95 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
@@ -17,12 +17,15 @@
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.ResultSet;
import com.google.inject.Provider;
+import java.util.EnumSet;
+
class ChangeIdPredicate extends OperatorPredicate<ChangeData> implements
- ChangeDataSource {
+ ChangeDataSource, Prefetchable {
private final Provider<ReviewDb> dbProvider;
ChangeIdPredicate(Provider<ReviewDb> dbProvider, String id) {
@@ -66,4 +69,9 @@
public int getCardinality() {
return ChangeCosts.CARD_KEY;
}
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ return EnumSet.of(NeededData.CHANGE);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index eccb2e8..62d2a27 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -29,10 +29,12 @@
import com.google.gerrit.server.config.WildProjectName;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.query.IntPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryBuilder;
import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -102,6 +104,7 @@
final ApprovalTypes approvalTypes;
final Project.NameKey wildProjectName;
final PatchListCache patchListCache;
+ final ProjectCache projectCache;
@Inject
Arguments(Provider<ReviewDb> dbProvider,
@@ -112,7 +115,7 @@
AccountResolver accountResolver, GroupCache groupCache,
AuthConfig authConfig, ApprovalTypes approvalTypes,
@WildProjectName Project.NameKey wildProjectName,
- PatchListCache patchListCache) {
+ PatchListCache patchListCache, ProjectCache projectCache) {
this.dbProvider = dbProvider;
this.rewriter = rewriter;
this.userFactory = userFactory;
@@ -124,6 +127,7 @@
this.approvalTypes = approvalTypes;
this.wildProjectName = wildProjectName;
this.patchListCache = patchListCache;
+ this.projectCache = projectCache;
}
}
@@ -272,7 +276,8 @@
@Operator
public Predicate<ChangeData> label(String name) {
return new LabelPredicate(args.changeControlGenericFactory,
- args.userFactory, args.dbProvider, args.approvalTypes, name);
+ args.userFactory, args.dbProvider, args.approvalTypes,
+ args.projectCache, name);
}
@Operator
@@ -318,13 +323,14 @@
// If its not an account, maybe its a group?
//
- AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(who));
+ AccountGroup g =
+ FutureUtil.get(args.groupCache.get(new AccountGroup.NameKey(who)));
if (g != null) {
return visibleto(new SingleGroupUser(args.authConfig, g.getId()));
}
- Collection<AccountGroup> matches =
- args.groupCache.get(new AccountGroup.ExternalNameKey(who));
+ Collection<AccountGroup> matches = FutureUtil.get( //
+ args.groupCache.get(new AccountGroup.ExternalNameKey(who))).getGroups();
if (matches != null && !matches.isEmpty()) {
HashSet<AccountGroup.Id> ids = new HashSet<AccountGroup.Id>();
for (AccountGroup group : matches) {
@@ -337,9 +343,7 @@
}
public Predicate<ChangeData> visibleto(CurrentUser user) {
- return new IsVisibleToPredicate(args.dbProvider, //
- args.changeControlGenericFactory, //
- user);
+ return new IsVisibleToPredicate(args.dbProvider, args.projectCache, user);
}
public Predicate<ChangeData> is_visible() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryModule.java
new file mode 100644
index 0000000..3bc5d4a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryModule.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.server.config.FactoryModule;
+
+/** Module for package-private predicates. */
+public class ChangeQueryModule extends FactoryModule {
+ @Override
+ protected void configure() {
+ factory(AndSource.Factory.class);
+ bind(ChangeQueryRewriter.class);
+ factory(ChangeQueryBuilder.Factory.class);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
index 904bc3c..f793d99 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryRewriter.java
@@ -38,24 +38,31 @@
new ChangeQueryBuilder.Arguments( //
new InvalidProvider<ReviewDb>(), //
new InvalidProvider<ChangeQueryRewriter>(), //
- null, null, null, null, null, null, null, null, null), null));
+ null, null, null, null, null, null, null, null, null, null), null));
private final Provider<ReviewDb> dbProvider;
+ private final AndSource.Factory andSourceFactory;
@Inject
- ChangeQueryRewriter(Provider<ReviewDb> dbProvider) {
+ ChangeQueryRewriter(Provider<ReviewDb> dbProvider, AndSource.Factory andSourceFactory) {
super(mydef);
this.dbProvider = dbProvider;
+ this.andSourceFactory = andSourceFactory;
}
@Override
public Predicate<ChangeData> and(Collection<? extends Predicate<ChangeData>> l) {
- return hasSource(l) ? new AndSource(l) : super.and(l);
+ return hasSource(l) ? andSourceFactory.create(l) : new PrefetchableAndPredicate(l);
}
@Override
public Predicate<ChangeData> or(Collection<? extends Predicate<ChangeData>> l) {
- return hasSource(l) ? new OrSource(l) : super.or(l);
+ return hasSource(l) ? new OrSource(l) : new PrefetchableOrPredicate(l);
+ }
+
+ @Override
+ public Predicate<ChangeData> not(Predicate<ChangeData> that) {
+ return new PrefetchableNotPredicate(that);
}
@Rewrite("-status:open")
@@ -125,7 +132,7 @@
@Named("P") final ProjectPredicate p,
@Named("S") final SortKeyPredicate.After s,
@Named("L") final IntPredicate<ChangeData> l) {
- return new PaginatedSource(500, s.getValue(), l.intValue()) {
+ return new PaginatedSource(500, s, l.intValue()) {
@Override
ResultSet<Change> scan(ChangeAccess a, String key, int limit)
throws OrmException {
@@ -146,7 +153,7 @@
@Named("P") final ProjectPredicate p,
@Named("S") final SortKeyPredicate.Before s,
@Named("L") final IntPredicate<ChangeData> l) {
- return new PaginatedSource(500, s.getValue(), l.intValue()) {
+ return new PaginatedSource(500, s, l.intValue()) {
@Override
ResultSet<Change> scan(ChangeAccess a, String key, int limit)
throws OrmException {
@@ -167,7 +174,7 @@
@Named("P") final ProjectPredicate p,
@Named("S") final SortKeyPredicate.After s,
@Named("L") final IntPredicate<ChangeData> l) {
- return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+ return new PaginatedSource(40000, s, l.intValue()) {
@Override
ResultSet<Change> scan(ChangeAccess a, String key, int limit)
throws OrmException {
@@ -189,7 +196,7 @@
@Named("P") final ProjectPredicate p,
@Named("S") final SortKeyPredicate.Before s,
@Named("L") final IntPredicate<ChangeData> l) {
- return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+ return new PaginatedSource(40000, s, l.intValue()) {
@Override
ResultSet<Change> scan(ChangeAccess a, String key, int limit)
throws OrmException {
@@ -211,7 +218,7 @@
@Named("P") final ProjectPredicate p,
@Named("S") final SortKeyPredicate.After s,
@Named("L") final IntPredicate<ChangeData> l) {
- return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+ return new PaginatedSource(40000, s, l.intValue()) {
@Override
ResultSet<Change> scan(ChangeAccess a, String key, int limit)
throws OrmException {
@@ -233,7 +240,7 @@
@Named("P") final ProjectPredicate p,
@Named("S") final SortKeyPredicate.Before s,
@Named("L") final IntPredicate<ChangeData> l) {
- return new PaginatedSource(40000, s.getValue(), l.intValue()) {
+ return new PaginatedSource(40000, s, l.intValue()) {
@Override
ResultSet<Change> scan(ChangeAccess a, String key, int limit)
throws OrmException {
@@ -254,7 +261,7 @@
public Predicate<ChangeData> r20_byOpenPrev(
@Named("S") final SortKeyPredicate.After s,
@Named("L") final IntPredicate<ChangeData> l) {
- return new PaginatedSource(2000, s.getValue(), l.intValue()) {
+ return new PaginatedSource(2000, s, l.intValue()) {
@Override
ResultSet<Change> scan(ChangeAccess a, String key, int limit)
throws OrmException {
@@ -272,7 +279,7 @@
public Predicate<ChangeData> r20_byOpenNext(
@Named("S") final SortKeyPredicate.Before s,
@Named("L") final IntPredicate<ChangeData> l) {
- return new PaginatedSource(2000, s.getValue(), l.intValue()) {
+ return new PaginatedSource(2000, s, l.intValue()) {
@Override
ResultSet<Change> scan(ChangeAccess a, String key, int limit)
throws OrmException {
@@ -291,7 +298,7 @@
public Predicate<ChangeData> r20_byMergedPrev(
@Named("S") final SortKeyPredicate.After s,
@Named("L") final IntPredicate<ChangeData> l) {
- return new PaginatedSource(50000, s.getValue(), l.intValue()) {
+ return new PaginatedSource(50000, s, l.intValue()) {
{
init("r20_byMergedPrev", s, l);
}
@@ -315,7 +322,7 @@
public Predicate<ChangeData> r20_byMergedNext(
@Named("S") final SortKeyPredicate.Before s,
@Named("L") final IntPredicate<ChangeData> l) {
- return new PaginatedSource(50000, s.getValue(), l.intValue()) {
+ return new PaginatedSource(50000, s, l.intValue()) {
{
init("r20_byMergedNext", s, l);
}
@@ -339,7 +346,7 @@
public Predicate<ChangeData> r20_byAbandonedPrev(
@Named("S") final SortKeyPredicate.After s,
@Named("L") final IntPredicate<ChangeData> l) {
- return new PaginatedSource(50000, s.getValue(), l.intValue()) {
+ return new PaginatedSource(50000, s, l.intValue()) {
{
init("r20_byAbandonedPrev", s, l);
}
@@ -363,7 +370,7 @@
public Predicate<ChangeData> r20_byAbandonedNext(
@Named("S") final SortKeyPredicate.Before s,
@Named("L") final IntPredicate<ChangeData> l) {
- return new PaginatedSource(50000, s.getValue(), l.intValue()) {
+ return new PaginatedSource(50000, s, l.intValue()) {
{
init("r20_byAbandonedNext", s, l);
}
@@ -601,12 +608,12 @@
private abstract class PaginatedSource extends ChangeSource implements
Paginated {
- private final String startKey;
+ private final SortKeyPredicate skp;
private final int limit;
- PaginatedSource(int card, String start, int lim) {
+ PaginatedSource(int card, SortKeyPredicate skp, int lim) {
super(card);
- this.startKey = start;
+ this.skp = skp;
this.limit = lim;
}
@@ -617,13 +624,13 @@
@Override
ResultSet<Change> scan(ChangeAccess a) throws OrmException {
- return scan(a, startKey, limit);
+ return scan(a, skp.getValue(), limit);
}
@Override
public ResultSet<ChangeData> restart(ChangeData last) throws OrmException {
return ChangeDataResultSet.change(scan(dbProvider.get().changes(), //
- last.change(dbProvider).getSortKey(), //
+ skp.nextKey(last.change(dbProvider)), //
limit));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
index 4ae2278..3dfdb96 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
@@ -18,11 +18,13 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.query.OperatorPredicate;
import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.EnumMap;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -35,7 +37,8 @@
* status:} but may also be {@code is:} to help do-what-i-meanery for end-users
* searching for changes. Either operator name has the same meaning.
*/
-final class ChangeStatusPredicate extends OperatorPredicate<ChangeData> {
+final class ChangeStatusPredicate extends OperatorPredicate<ChangeData>
+ implements Prefetchable {
private static final Map<String, Change.Status> byName;
private static final EnumMap<Change.Status, String> byEnum;
@@ -106,6 +109,11 @@
}
@Override
+ public EnumSet<NeededData> getNeededData() {
+ return EnumSet.of(NeededData.CHANGE);
+ }
+
+ @Override
public int hashCode() {
return status.hashCode();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
index c03cddc..16b702a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
@@ -18,6 +18,7 @@
import com.google.gerrit.reviewdb.RevId;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.query.ObjectIdPredicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.ResultSet;
import com.google.inject.Provider;
@@ -25,8 +26,10 @@
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.ObjectId;
+import java.util.EnumSet;
+
class CommitPredicate extends ObjectIdPredicate<ChangeData> implements
- ChangeDataSource {
+ ChangeDataSource, Prefetchable {
private final Provider<ReviewDb> dbProvider;
CommitPredicate(Provider<ReviewDb> dbProvider, AbbreviatedObjectId id) {
@@ -74,4 +77,9 @@
public int getCost() {
return ChangeCosts.cost(ChangeCosts.PATCH_SETS_SCAN, getCardinality());
}
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ return EnumSet.of(NeededData.PATCHES);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
index 07d4dd2..820345f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByPredicate.java
@@ -19,16 +19,18 @@
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.ResultSet;
import com.google.gwtorm.client.impl.ListResultSet;
import com.google.inject.Provider;
import java.util.ArrayList;
+import java.util.EnumSet;
import java.util.HashSet;
class HasDraftByPredicate extends OperatorPredicate<ChangeData> implements
- ChangeDataSource {
+ ChangeDataSource, Prefetchable {
private final Provider<ReviewDb> db;
private final Account.Id accountId;
@@ -78,4 +80,9 @@
public int getCost() {
return ChangeCosts.cost(ChangeCosts.PATCH_SETS_SCAN, getCardinality());
}
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ return EnumSet.of(NeededData.COMMENTS);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
index 46c7741..7003b69 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
@@ -19,10 +19,14 @@
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Provider;
-class IsReviewedPredicate extends OperatorPredicate<ChangeData> {
+import java.util.EnumSet;
+
+class IsReviewedPredicate extends OperatorPredicate<ChangeData> implements
+ Prefetchable {
private final Provider<ReviewDb> dbProvider;
IsReviewedPredicate(Provider<ReviewDb> dbProvider) {
@@ -51,4 +55,9 @@
public int getCost() {
return 2;
}
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ return EnumSet.of(NeededData.APPROVALS, NeededData.CHANGE);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
index 1e9d405..8bfe885 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsVisibleToPredicate.java
@@ -18,13 +18,16 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Provider;
-class IsVisibleToPredicate extends OperatorPredicate<ChangeData> {
+import java.util.EnumSet;
+
+class IsVisibleToPredicate extends OperatorPredicate<ChangeData> implements
+ Prefetchable {
private static String describe(CurrentUser user) {
if (user instanceof IdentifiedUser) {
return ((IdentifiedUser) user).getAccountId().toString();
@@ -37,14 +40,14 @@
}
private final Provider<ReviewDb> db;
- private final ChangeControl.GenericFactory changeControl;
private final CurrentUser user;
+ private final ProjectCache projectCache;
- IsVisibleToPredicate(Provider<ReviewDb> db,
- ChangeControl.GenericFactory changeControlFactory, CurrentUser user) {
+ IsVisibleToPredicate(Provider<ReviewDb> db, ProjectCache projectCache,
+ CurrentUser user) {
super(ChangeQueryBuilder.FIELD_VISIBLETO, describe(user));
this.db = db;
- this.changeControl = changeControlFactory;
+ this.projectCache = projectCache;
this.user = user;
}
@@ -53,15 +56,12 @@
if (cd.fastIsVisibleTo(user)) {
return true;
}
- try {
- Change c = cd.change(db);
- if (c != null && changeControl.controlFor(c, user).isVisible()) {
- cd.cacheVisibleTo(user);
- return true;
- } else {
- return false;
- }
- } catch (NoSuchChangeException e) {
+ Change c = cd.change(db);
+ if (c != null
+ && cd.projectState(db, projectCache).controlFor(user).controlFor(c).isVisible()) {
+ cd.cacheVisibleTo(user);
+ return true;
+ } else {
return false;
}
}
@@ -70,4 +70,9 @@
public int getCost() {
return 1;
}
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ return EnumSet.of(NeededData.PROJECT_STATE, NeededData.CHANGE);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
index 870be73..f9e2200 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
@@ -17,19 +17,23 @@
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.reviewdb.Project.NameKey;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.query.OperatorPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import java.util.ArrayList;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-class IsWatchedByPredicate extends OperatorPredicate<ChangeData> {
+class IsWatchedByPredicate extends OperatorPredicate<ChangeData> implements
+ Prefetchable {
private static String describe(CurrentUser user) {
if (user instanceof IdentifiedUser) {
return ((IdentifiedUser) user).getAccountId().toString();
@@ -50,22 +54,7 @@
@Override
public boolean match(final ChangeData cd) throws OrmException {
- if (rules == null) {
- ChangeQueryBuilder builder = new ChangeQueryBuilder(args, user);
- rules = new HashMap<Project.NameKey, List<Predicate<ChangeData>>>();
- for (AccountProjectWatch w : user.getNotificationFilters()) {
- List<Predicate<ChangeData>> list = rules.get(w.getProjectNameKey());
- if (list == null) {
- list = new ArrayList<Predicate<ChangeData>>(4);
- rules.put(w.getProjectNameKey(), list);
- }
-
- Predicate<ChangeData> p = compile(builder, w);
- if (p != null) {
- list.add(p);
- }
- }
- }
+ Map<Project.NameKey, List<Predicate<ChangeData>>> rules = getRules();
if (rules.isEmpty()) {
return false;
@@ -91,6 +80,26 @@
return false;
}
+ private Map<Project.NameKey, List<Predicate<ChangeData>>> getRules() {
+ if (rules == null) {
+ rules = new HashMap<Project.NameKey, List<Predicate<ChangeData>>>();
+ ChangeQueryBuilder builder = new ChangeQueryBuilder(args, user);
+ for (AccountProjectWatch w : user.getNotificationFilters()) {
+ List<Predicate<ChangeData>> list = rules.get(w.getProjectNameKey());
+ if (list == null) {
+ list = new ArrayList<Predicate<ChangeData>>(4);
+ rules.put(w.getProjectNameKey(), list);
+ }
+
+ Predicate<ChangeData> p = compile(builder, w);
+ if (p != null) {
+ list.add(p);
+ }
+ }
+ }
+ return rules;
+ }
+
@SuppressWarnings("unchecked")
private Predicate<ChangeData> compile(ChangeQueryBuilder builder,
AccountProjectWatch w) {
@@ -118,4 +127,22 @@
public int getCost() {
return 1;
}
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ Map<NameKey, List<Predicate<ChangeData>>> rules = getRules();
+ if (rules.isEmpty()) {
+ return EnumSet.noneOf(NeededData.class);
+ }
+
+ EnumSet<NeededData> needed = EnumSet.of(NeededData.CHANGE);
+ for (List<Predicate<ChangeData>> list : rules.values()) {
+ for (Predicate<ChangeData> p : list) {
+ if (p instanceof Prefetchable) {
+ needed.addAll(((Prefetchable) p).getNeededData());
+ }
+ }
+ }
+ return needed;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
index e76c278..b3d3621 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -17,19 +17,23 @@
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.reviewdb.ApprovalCategory;
+import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Provider;
+import java.util.EnumSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-class LabelPredicate extends OperatorPredicate<ChangeData> {
+class LabelPredicate extends OperatorPredicate<ChangeData> implements
+ Prefetchable {
private static enum Test {
EQ {
@Override
@@ -102,17 +106,19 @@
private final ChangeControl.GenericFactory ccFactory;
private final IdentifiedUser.GenericFactory userFactory;
private final Provider<ReviewDb> dbProvider;
+ private final ProjectCache projectCache;
private final Test test;
private final ApprovalCategory.Id category;
private final short expVal;
LabelPredicate(ChangeControl.GenericFactory ccFactory,
IdentifiedUser.GenericFactory userFactory, Provider<ReviewDb> dbProvider,
- ApprovalTypes types, String value) {
+ ApprovalTypes types, ProjectCache projectCache, String value) {
super(ChangeQueryBuilder.FIELD_LABEL, value);
this.ccFactory = ccFactory;
this.userFactory = userFactory;
this.dbProvider = dbProvider;
+ this.projectCache = projectCache;
Matcher m1 = Pattern.compile("(=|>=|<=)([+-]?\\d+)$").matcher(value);
Matcher m2 = Pattern.compile("([+-]\\d+)$").matcher(value);
@@ -141,20 +147,18 @@
if (test.match(psVal, expVal)) {
// Double check the value is still permitted for the user.
//
- try {
- ChangeControl cc = ccFactory.controlFor(object.change(dbProvider), //
- userFactory.create(dbProvider, p.getAccountId()));
- if (!cc.isVisible()) {
- // The user can't see the change anymore.
- //
- continue;
- }
- psVal = cc.normalize(category, psVal);
- } catch (NoSuchChangeException e) {
- // The project has disappeared.
+ Change c = object.change(dbProvider);
+ IdentifiedUser user =
+ userFactory.create(dbProvider, p.getAccountId());
+ ChangeControl cc =
+ object.projectState(dbProvider, projectCache).controlFor(user)
+ .controlFor(c);
+ if (!cc.isVisible()) {
+ // The user can't see the change anymore.
//
continue;
}
+ psVal = cc.normalize(category, psVal);
if (test.match(psVal, expVal)) {
return true;
@@ -169,4 +173,10 @@
public int getCost() {
return 2;
}
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ return EnumSet.of(NeededData.APPROVALS, NeededData.CHANGE,
+ NeededData.PROJECT_STATE);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java
index 617a14a..756fe90 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.query.change;
import com.google.gerrit.reviewdb.Change;
-import com.google.gerrit.server.query.OrPredicate;
import com.google.gerrit.server.query.Predicate;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.ResultSet;
@@ -25,7 +24,7 @@
import java.util.Collection;
import java.util.HashSet;
-class OrSource extends OrPredicate<ChangeData> implements ChangeDataSource {
+class OrSource extends PrefetchableOrPredicate implements ChangeDataSource {
private int cardinality = -1;
OrSource(final Collection<? extends Predicate<ChangeData>> that) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java
index 224dce9..55fb685 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OwnerPredicate.java
@@ -18,10 +18,14 @@
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Provider;
-class OwnerPredicate extends OperatorPredicate<ChangeData> {
+import java.util.EnumSet;
+
+class OwnerPredicate extends OperatorPredicate<ChangeData> implements
+ Prefetchable {
private final Provider<ReviewDb> dbProvider;
private final Account.Id id;
@@ -45,4 +49,9 @@
public int getCost() {
return 1;
}
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ return EnumSet.of(NeededData.CHANGE);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateModule.java
similarity index 61%
copy from gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateModule.java
index 1eb1a4f..9f283d0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateModule.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,15 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.account;
+package com.google.gerrit.server.query.change;
-import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.server.config.FactoryModule;
-import java.util.Set;
-
-/** Translates an email address to a set of matching accounts. */
-public interface AccountByEmailCache {
- public Set<Account.Id> get(String email);
-
- public void evict(String email);
+/** Module for package-private predicates. */
+public class PredicateModule extends FactoryModule {
+ @Override
+ protected void configure() {
+ factory(AndSource.Factory.class);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/Prefetchable.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/Prefetchable.java
new file mode 100644
index 0000000..c0c904b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/Prefetchable.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import java.util.EnumSet;
+
+/**
+ * Predicates that rely on ChangeData need to give some information about what
+ * data from ChangeData will be needed. This enables better caching and
+ * fetching.
+ */
+interface Prefetchable {
+
+ /**
+ * @return the set of data that will be needed by this predicate or its
+ * children.
+ */
+ public EnumSet<ChangeData.NeededData> getNeededData();
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PrefetchableAndPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PrefetchableAndPredicate.java
new file mode 100644
index 0000000..940c816
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PrefetchableAndPredicate.java
@@ -0,0 +1,46 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.server.query.AndPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
+
+import java.util.Collection;
+import java.util.EnumSet;
+
+class PrefetchableAndPredicate extends AndPredicate<ChangeData>
+ implements Prefetchable {
+
+ PrefetchableAndPredicate(
+ Collection<? extends Predicate<ChangeData>> that) {
+ super(that);
+ }
+
+ PrefetchableAndPredicate(Predicate<ChangeData>... that) {
+ super(that);
+ }
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ EnumSet<NeededData> needed = EnumSet.noneOf(NeededData.class);
+ for (Predicate<ChangeData> p : getChildren()) {
+ if (p instanceof Prefetchable) {
+ needed.addAll(((Prefetchable) p).getNeededData());
+ }
+ }
+ return needed;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PrefetchableNotPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PrefetchableNotPredicate.java
new file mode 100644
index 0000000..a8c65c9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PrefetchableNotPredicate.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.server.query.NotPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
+
+import java.util.EnumSet;
+
+class PrefetchableNotPredicate extends NotPredicate<ChangeData>
+ implements Prefetchable {
+ PrefetchableNotPredicate(Predicate<ChangeData> that) {
+ super(that);
+ }
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ EnumSet<NeededData> needed = EnumSet.noneOf(NeededData.class);
+ for (Predicate<ChangeData> p : getChildren()) {
+ if (p instanceof Prefetchable) {
+ needed.addAll(((Prefetchable) p).getNeededData());
+ }
+ }
+ return needed;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PrefetchableOrPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PrefetchableOrPredicate.java
new file mode 100644
index 0000000..c8cdf23
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PrefetchableOrPredicate.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.change;
+
+import com.google.gerrit.server.query.OrPredicate;
+import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
+
+import java.util.Collection;
+import java.util.EnumSet;
+
+class PrefetchableOrPredicate extends OrPredicate<ChangeData> implements
+ Prefetchable {
+ PrefetchableOrPredicate(Predicate<ChangeData>... that) {
+ super(that);
+ }
+
+ PrefetchableOrPredicate(
+ Collection<? extends Predicate<ChangeData>> that) {
+ super(that);
+ }
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ EnumSet<NeededData> needed = EnumSet.noneOf(NeededData.class);
+ for (Predicate<ChangeData> p : getChildren()) {
+ if (p instanceof Prefetchable) {
+ needed.addAll(((Prefetchable) p).getNeededData());
+ }
+ }
+ return needed;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java
index f5f83e2..75cfa95 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RefPredicate.java
@@ -17,10 +17,14 @@
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Provider;
-class RefPredicate extends OperatorPredicate<ChangeData> {
+import java.util.EnumSet;
+
+class RefPredicate extends OperatorPredicate<ChangeData> implements
+ Prefetchable {
private final Provider<ReviewDb> dbProvider;
RefPredicate(Provider<ReviewDb> dbProvider, String ref) {
@@ -41,4 +45,9 @@
public int getCost() {
return 1;
}
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ return EnumSet.of(NeededData.CHANGE);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
index bcece94..89a8fd1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
@@ -18,10 +18,14 @@
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Provider;
-class ReviewerPredicate extends OperatorPredicate<ChangeData> {
+import java.util.EnumSet;
+
+class ReviewerPredicate extends OperatorPredicate<ChangeData> implements
+ Prefetchable {
private final Provider<ReviewDb> dbProvider;
private final Account.Id id;
@@ -49,4 +53,9 @@
public int getCost() {
return 2;
}
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ return EnumSet.of(NeededData.APPROVALS);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java
index 3ecc596..c5e4e7f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SortKeyPredicate.java
@@ -4,11 +4,16 @@
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Provider;
-abstract class SortKeyPredicate extends OperatorPredicate<ChangeData> {
+import java.util.EnumSet;
+
+abstract class SortKeyPredicate extends OperatorPredicate<ChangeData> implements
+ Prefetchable {
protected final Provider<ReviewDb> dbProvider;
SortKeyPredicate(Provider<ReviewDb> dbProvider, String name, String value) {
@@ -21,15 +26,26 @@
return 1;
}
+ abstract String nextKey(Change last);
+
+ public EnumSet<NeededData> getNeededData() {
+ return EnumSet.of(NeededData.CHANGE);
+ }
+
static class Before extends SortKeyPredicate {
Before(Provider<ReviewDb> dbProvider, String value) {
- super(dbProvider, "sortkey_before", value);
+ super(dbProvider, "sortkey_before", ChangeUtil.invertSortKey(value));
}
@Override
public boolean match(ChangeData cd) throws OrmException {
- Change change = cd.change(dbProvider);
- return change != null && change.getSortKey().compareTo(getValue()) < 0;
+ Change c = cd.change(dbProvider);
+ return c != null && c.getSortKeyDesc().compareTo(getValue()) > 0;
+ }
+
+ @Override
+ String nextKey(Change last) {
+ return last.getSortKeyDesc();
}
}
@@ -43,5 +59,10 @@
Change change = cd.change(dbProvider);
return change != null && change.getSortKey().compareTo(getValue()) > 0;
}
+
+ @Override
+ String nextKey(Change last) {
+ return last.getSortKey();
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
index 7bc972d..cc736d6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TopicPredicate.java
@@ -17,10 +17,14 @@
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Provider;
-class TopicPredicate extends OperatorPredicate<ChangeData> {
+import java.util.EnumSet;
+
+class TopicPredicate extends OperatorPredicate<ChangeData> implements
+ Prefetchable {
private final Provider<ReviewDb> dbProvider;
TopicPredicate(Provider<ReviewDb> dbProvider, String topic) {
@@ -41,4 +45,9 @@
public int getCost() {
return 1;
}
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ return EnumSet.of(NeededData.CHANGE);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
index eef568d..27194a5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TrackingIdPredicate.java
@@ -18,16 +18,18 @@
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.TrackingId;
import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.server.query.change.ChangeData.NeededData;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.ResultSet;
import com.google.gwtorm.client.impl.ListResultSet;
import com.google.inject.Provider;
import java.util.ArrayList;
+import java.util.EnumSet;
import java.util.HashSet;
class TrackingIdPredicate extends OperatorPredicate<ChangeData> implements
- ChangeDataSource {
+ ChangeDataSource, Prefetchable {
private final Provider<ReviewDb> db;
TrackingIdPredicate(Provider<ReviewDb> db, String trackingId) {
@@ -74,4 +76,9 @@
public int getCost() {
return ChangeCosts.cost(ChangeCosts.TR_SCAN, getCardinality());
}
+
+ @Override
+ public EnumSet<NeededData> getNeededData() {
+ return EnumSet.of(NeededData.TRACKING_IDS);
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java
index 701e97a..e7d28b9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DataSourceProvider.java
@@ -15,13 +15,19 @@
package com.google.gerrit.server.schema;
import static com.google.gerrit.server.config.ConfigUtil.getEnum;
-import static java.util.concurrent.TimeUnit.*;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.gerrit.lifecycle.LifecycleListener;
+import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.SchemaFactory;
+import com.google.gwtorm.jdbc.Database;
import com.google.gwtorm.jdbc.SimpleDataSource;
+import com.google.gwtorm.nosql.heap.FileDatabase;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
@@ -29,44 +35,53 @@
import org.apache.commons.dbcp.BasicDataSource;
import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Properties;
-import javax.sql.DataSource;
-
/** Provides access to the DataSource. */
@Singleton
-public final class DataSourceProvider implements Provider<DataSource>,
- LifecycleListener {
- private final DataSource ds;
+public final class DataSourceProvider implements
+ Provider<SchemaFactory<ReviewDb>>, LifecycleListener {
+ private static final Logger log =
+ LoggerFactory.getLogger(DataSourceProvider.class);
+
+ private final MyProvider ds;
@Inject
DataSourceProvider(final SitePaths site,
@GerritServerConfig final Config cfg, Context ctx) {
- ds = open(site, cfg, ctx);
+ try {
+ Type type = getEnum(cfg, "database", null, "type", Type.values(), null);
+ if (type == DataSourceProvider.Type.NOSQL_HEAP_FILE) {
+ ds = new NoSqlHeapFileProvider(site, cfg, ctx);
+
+ } else {
+ ds = new JdbcProvider(site, cfg, ctx);
+ }
+ } catch (OrmException e) {
+ throw new ProvisionException("Cannot create ReviewDb", e);
+ }
}
@Override
- public synchronized DataSource get() {
- return ds;
+ public SchemaFactory<ReviewDb> get() {
+ return ds.get();
}
@Override
public void start() {
+ ds.start();
}
+ @SuppressWarnings("unchecked")
@Override
public synchronized void stop() {
- if (ds instanceof BasicDataSource) {
- try {
- ((BasicDataSource) ds).close();
- } catch (SQLException e) {
- // Ignore the close failure.
- }
- }
+ ds.stop();
}
public static enum Context {
@@ -74,141 +89,11 @@
}
public static enum Type {
- H2, POSTGRESQL, MYSQL, JDBC;
- }
+ // Traditional SQL databases
+ H2, POSTGRESQL, MYSQL, JDBC,
- private DataSource open(final SitePaths site, final Config cfg,
- final Context context) {
- Type type = getEnum(cfg, "database", null, "type", Type.values(), null);
- String driver = optional(cfg, "driver");
- String url = optional(cfg, "url");
- String username = optional(cfg, "username");
- String password = optional(cfg, "password");
-
- if (url == null || url.isEmpty()) {
- if (type == null) {
- if (url != null && !url.isEmpty()) {
- type = Type.JDBC;
- } else {
- type = Type.H2;
- }
- }
-
- switch (type) {
- case H2: {
- String database = optional(cfg, "database");
- if (database == null || database.isEmpty()) {
- database = "db/ReviewDB";
- }
- File db = site.resolve(database);
- try {
- db = db.getCanonicalFile();
- } catch (IOException e) {
- db = db.getAbsoluteFile();
- }
- url = "jdbc:h2:" + db.toURI().toString();
- break;
- }
-
- case POSTGRESQL: {
- final StringBuilder b = new StringBuilder();
- b.append("jdbc:postgresql://");
- b.append(hostname(optional(cfg, "hostname")));
- b.append(port(optional(cfg, "port")));
- b.append("/");
- b.append(required(cfg, "database"));
- url = b.toString();
- break;
- }
-
- case MYSQL: {
- final StringBuilder b = new StringBuilder();
- b.append("jdbc:mysql://");
- b.append(hostname(optional(cfg, "hostname")));
- b.append(port(optional(cfg, "port")));
- b.append("/");
- b.append(required(cfg, "database"));
- url = b.toString();
- break;
- }
-
- case JDBC:
- driver = required(cfg, "driver");
- url = required(cfg, "url");
- break;
-
- default:
- throw new IllegalArgumentException(type + " not supported");
- }
- }
-
- if (driver == null || driver.isEmpty()) {
- if (url.startsWith("jdbc:h2:")) {
- driver = "org.h2.Driver";
-
- } else if (url.startsWith("jdbc:postgresql:")) {
- driver = "org.postgresql.Driver";
-
- } else if (url.startsWith("jdbc:mysql:")) {
- driver = "com.mysql.jdbc.Driver";
-
- } else {
- throw new IllegalArgumentException("database.driver must be set");
- }
- }
-
- boolean usePool;
- if (url.startsWith("jdbc:mysql:")) {
- // MySQL has given us trouble with the connection pool,
- // sometimes the backend disconnects and the pool winds
- // up with a stale connection. Fortunately opening up
- // a new MySQL connection is usually very fast.
- //
- usePool = false;
- } else {
- usePool = true;
- }
- usePool = cfg.getBoolean("database", "connectionpool", usePool);
- if (context == Context.SINGLE_USER) {
- usePool = false;
- }
-
- if (usePool) {
- final BasicDataSource ds = new BasicDataSource();
- ds.setDriverClassName(driver);
- ds.setUrl(url);
- if (username != null && !username.isEmpty()) {
- ds.setUsername(username);
- }
- if (password != null && !password.isEmpty()) {
- ds.setPassword(password);
- }
- ds.setMaxActive(cfg.getInt("database", "poollimit", 8));
- ds.setMinIdle(cfg.getInt("database", "poolminidle", 4));
- ds.setMaxIdle(cfg.getInt("database", "poolmaxidle", 4));
- ds.setMaxWait(ConfigUtil.getTimeUnit(cfg, "database", null,
- "poolmaxwait", MILLISECONDS.convert(30, SECONDS), MILLISECONDS));
- ds.setInitialSize(ds.getMinIdle());
- return ds;
-
- } else {
- // Don't use the connection pool.
- //
- try {
- final Properties p = new Properties();
- p.setProperty("driver", driver);
- p.setProperty("url", url);
- if (username != null) {
- p.setProperty("user", username);
- }
- if (password != null) {
- p.setProperty("password", password);
- }
- return new SimpleDataSource(p);
- } catch (SQLException se) {
- throw new ProvisionException("Database unavailable", se);
- }
- }
+ // NoSQL types we also support
+ NOSQL_HEAP_FILE;
}
private static String hostname(String hostname) {
@@ -239,4 +124,208 @@
}
return v;
}
+
+ private abstract static class MyProvider implements
+ Provider<SchemaFactory<ReviewDb>>, LifecycleListener {
+ }
+
+ private static class NoSqlHeapFileProvider extends MyProvider {
+ private FileDatabase<ReviewDb> db;
+
+ NoSqlHeapFileProvider(final SitePaths site, final Config cfg,
+ final Context context) throws OrmException {
+ String database = optional(cfg, "database");
+ if (database == null || database.isEmpty()) {
+ database = "db/ReviewDB";
+ }
+ File path = site.resolve(database);
+ try {
+ path = path.getCanonicalFile();
+ } catch (IOException e) {
+ path = path.getAbsoluteFile();
+ }
+ db = new FileDatabase<ReviewDb>(path, ReviewDb.class);
+ }
+
+ @Override
+ public SchemaFactory<ReviewDb> get() {
+ return db;
+ }
+
+ @Override
+ public void start() {
+ }
+
+ @Override
+ public void stop() {
+ if (db != null) {
+ try {
+ db.close();
+ } catch (OrmException err) {
+ log.warn("Cannot safely close database", err);
+ } finally {
+ db = null;
+ }
+ }
+ }
+ }
+
+ private static class JdbcProvider extends MyProvider {
+ private Database<ReviewDb> db;
+ private BasicDataSource dataSource;
+
+ JdbcProvider(final SitePaths site, final Config cfg, final Context context)
+ throws OrmException {
+ Type type = getEnum(cfg, "database", null, "type", Type.values(), null);
+ String driver = optional(cfg, "driver");
+ String url = optional(cfg, "url");
+ String username = optional(cfg, "username");
+ String password = optional(cfg, "password");
+
+ if (url == null || url.isEmpty()) {
+ if (type == null) {
+ type = Type.H2;
+ }
+
+ switch (type) {
+ case H2: {
+ String database = optional(cfg, "database");
+ if (database == null || database.isEmpty()) {
+ database = "db/ReviewDB";
+ }
+ File db = site.resolve(database);
+ try {
+ db = db.getCanonicalFile();
+ } catch (IOException e) {
+ db = db.getAbsoluteFile();
+ }
+ url = "jdbc:h2:" + db.toURI().toString();
+ break;
+ }
+
+ case POSTGRESQL: {
+ final StringBuilder b = new StringBuilder();
+ b.append("jdbc:postgresql://");
+ b.append(hostname(optional(cfg, "hostname")));
+ b.append(port(optional(cfg, "port")));
+ b.append("/");
+ b.append(required(cfg, "database"));
+ url = b.toString();
+ break;
+ }
+
+ case MYSQL: {
+ final StringBuilder b = new StringBuilder();
+ b.append("jdbc:mysql://");
+ b.append(hostname(optional(cfg, "hostname")));
+ b.append(port(optional(cfg, "port")));
+ b.append("/");
+ b.append(required(cfg, "database"));
+ url = b.toString();
+ break;
+ }
+
+ case JDBC:
+ driver = required(cfg, "driver");
+ url = required(cfg, "url");
+ break;
+
+ default:
+ throw new IllegalArgumentException(type + " not supported");
+ }
+ }
+
+ if (driver == null || driver.isEmpty()) {
+ if (url.startsWith("jdbc:h2:")) {
+ driver = "org.h2.Driver";
+
+ } else if (url.startsWith("jdbc:postgresql:")) {
+ driver = "org.postgresql.Driver";
+
+ } else if (url.startsWith("jdbc:mysql:")) {
+ driver = "com.mysql.jdbc.Driver";
+
+ } else {
+ throw new IllegalArgumentException("database.driver must be set");
+ }
+ }
+
+ boolean usePool;
+ if (url.startsWith("jdbc:mysql:")) {
+ // MySQL has given us trouble with the connection pool,
+ // sometimes the backend disconnects and the pool winds
+ // up with a stale connection. Fortunately opening up
+ // a new MySQL connection is usually very fast.
+ //
+ usePool = false;
+ } else {
+ usePool = true;
+ }
+ usePool = cfg.getBoolean("database", "connectionpool", usePool);
+ if (context == Context.SINGLE_USER) {
+ usePool = false;
+ }
+
+ if (usePool) {
+ final BasicDataSource ds = new BasicDataSource();
+ ds.setDriverClassName(driver);
+ ds.setUrl(url);
+ if (username != null && !username.isEmpty()) {
+ ds.setUsername(username);
+ }
+ if (password != null && !password.isEmpty()) {
+ ds.setPassword(password);
+ }
+ ds.setMaxActive(cfg.getInt("database", "poollimit", 8));
+ ds.setMinIdle(cfg.getInt("database", "poolminidle", 4));
+ ds.setMaxIdle(cfg.getInt("database", "poolmaxidle", 4));
+ ds.setMaxWait(ConfigUtil.getTimeUnit(cfg, "database", null,
+ "poolmaxwait", MILLISECONDS.convert(30, SECONDS), MILLISECONDS));
+ ds.setInitialSize(ds.getMinIdle());
+ dataSource = ds;
+ db = new Database<ReviewDb>(ds, ReviewDb.class);
+
+ } else {
+ // Don't use the connection pool.
+ //
+ try {
+ final Properties p = new Properties();
+ p.setProperty("driver", driver);
+ p.setProperty("url", url);
+ if (username != null) {
+ p.setProperty("user", username);
+ }
+ if (password != null) {
+ p.setProperty("password", password);
+ }
+ db = new Database<ReviewDb>(new SimpleDataSource(p), ReviewDb.class);
+ } catch (SQLException se) {
+ throw new ProvisionException("Database unavailable", se);
+ }
+ }
+ }
+
+ @Override
+ public SchemaFactory<ReviewDb> get() {
+ return db;
+ }
+
+ @Override
+ public void start() {
+ }
+
+ @Override
+ public void stop() {
+ if (dataSource != null) {
+ try {
+ dataSource.close();
+ } catch (SQLException e) {
+ // Ignore the close failure.
+ } finally {
+ dataSource = null;
+ db = null;
+ }
+ }
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DatabaseModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DatabaseModule.java
deleted file mode 100644
index 55a36d6..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DatabaseModule.java
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.schema;
-
-import static com.google.inject.Scopes.SINGLETON;
-
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.server.config.FactoryModule;
-import com.google.gwtorm.client.SchemaFactory;
-import com.google.gwtorm.jdbc.Database;
-import com.google.inject.TypeLiteral;
-
-/** Loads the database with standard dependencies. */
-public class DatabaseModule extends FactoryModule {
- @Override
- protected void configure() {
- install(new SchemaVersion.Module());
-
- bind(new TypeLiteral<SchemaFactory<ReviewDb>>() {}).to(
- new TypeLiteral<Database<ReviewDb>>() {}).in(SINGLETON);
- bind(new TypeLiteral<Database<ReviewDb>>() {}).toProvider(
- ReviewDbDatabaseProvider.class).in(SINGLETON);
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ReviewDbDatabaseProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ReviewDbDatabaseProvider.java
deleted file mode 100644
index 56b0379..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ReviewDbDatabaseProvider.java
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.schema;
-
-import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gwtorm.client.OrmException;
-import com.google.gwtorm.jdbc.Database;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
-import com.google.inject.name.Named;
-
-import javax.sql.DataSource;
-
-/** Provides the {@code Database<ReviewDb>} database handle. */
-final class ReviewDbDatabaseProvider implements Provider<Database<ReviewDb>> {
- private final DataSource datasource;
-
- @Inject
- ReviewDbDatabaseProvider(@Named("ReviewDb") final DataSource ds) {
- datasource = ds;
- }
-
- @Override
- public Database<ReviewDb> get() {
- try {
- return new Database<ReviewDb>(datasource, ReviewDb.class);
- } catch (OrmException e) {
- throw new ProvisionException("Cannot create ReviewDb", e);
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
index 302e22b..5460bed 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -29,6 +29,7 @@
import com.google.gerrit.server.workflow.SubmitFunction;
import com.google.gwtjsonrpc.server.SignedToken;
import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.StatementExecutor;
import com.google.gwtorm.jdbc.JdbcExecutor;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.schema.sql.DialectH2;
@@ -71,12 +72,21 @@
}
public void create(final ReviewDb db) throws OrmException {
- final JdbcSchema jdbc = (JdbcSchema) db;
- final JdbcExecutor e = new JdbcExecutor(jdbc);
- try {
- jdbc.updateSchema(e);
- } finally {
- e.close();
+ if (db instanceof JdbcSchema) {
+ final JdbcSchema jdbc = (JdbcSchema) db;
+ final JdbcExecutor e = new JdbcExecutor(jdbc);
+ try {
+ jdbc.updateSchema(e);
+ } finally {
+ e.close();
+ }
+ } else {
+ db.updateSchema(new StatementExecutor() {
+ @Override
+ public void execute(String sql) throws OrmException {
+ throw new OrmException("Raw SQL not supported");
+ }
+ });
}
final CurrentSchemaVersion sVer = CurrentSchemaVersion.create();
@@ -94,19 +104,21 @@
initForgeIdentityCategory(db, sConfig);
initWildCardProject(db);
- final SqlDialect d = jdbc.getDialect();
- if (d instanceof DialectH2) {
- index_generic.run(db);
+ if (db instanceof JdbcSchema) {
+ final SqlDialect d = ((JdbcSchema) db).getDialect();
+ if (d instanceof DialectH2) {
+ index_generic.run(db);
- } else if (d instanceof DialectMySQL) {
- index_generic.run(db);
- mysql_nextval.run(db);
+ } else if (d instanceof DialectMySQL) {
+ index_generic.run(db);
+ mysql_nextval.run(db);
- } else if (d instanceof DialectPostgreSQL) {
- index_postgres.run(db);
+ } else if (d instanceof DialectPostgreSQL) {
+ index_postgres.run(db);
- } else {
- throw new OrmException("Unsupported database " + d.getClass().getName());
+ } else {
+ throw new OrmException("Unsupported database " + d.getClass().getName());
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index 78edbc3..7df24b4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -32,7 +32,7 @@
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
- private static final Class<? extends SchemaVersion> C = Schema_41.class;
+ private static final Class<? extends SchemaVersion> C = Schema_103.class;
public static class Module extends AbstractModule {
@Override
@@ -81,22 +81,20 @@
/** Runs check on the prior schema version, and then upgrades. */
protected void upgradeFrom(UpdateUI ui, CurrentSchemaVersion curr, ReviewDb db, boolean toTargetVersion)
throws OrmException, SQLException {
- final JdbcSchema s = (JdbcSchema) db;
-
prior.get().check(ui, curr, db, false);
ui.message("Upgrading database schema from version " + curr.versionNbr
+ " to " + versionNbr + " ...");
preUpdateSchema(db);
- final JdbcExecutor e = new JdbcExecutor(s);
+ final StatementExecutor e = newExecutor(db);
try {
- s.updateSchema(e);
+ db.updateSchema(e);
migrateData(db, ui);
if (toTargetVersion) {
final List<String> pruneList = new ArrayList<String>();
- s.pruneSchema(new StatementExecutor() {
+ db.pruneSchema(new StatementExecutor() {
public void execute(String sql) {
pruneList.add(sql);
}
@@ -107,11 +105,25 @@
}
}
} finally {
- e.close();
+ if (e instanceof JdbcExecutor) {
+ ((JdbcExecutor)e).close();
+ }
}
finish(curr, db);
}
+ private StatementExecutor newExecutor(ReviewDb db) throws OrmException {
+ if (db instanceof JdbcSchema) {
+ return new JdbcExecutor((JdbcSchema) db);
+ }
+ return new StatementExecutor() {
+ @Override
+ public void execute(String sql) throws OrmException {
+ throw new OrmException("SQL statement execution not supported");
+ }
+ };
+ }
+
/** Invoke before updateSchema adds new columns/tables. */
protected void preUpdateSchema(ReviewDb db) throws OrmException, SQLException {
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_103.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_103.java
new file mode 100644
index 0000000..5474638
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_103.java
@@ -0,0 +1,140 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.schema.sql.DialectMySQL;
+import com.google.gwtorm.schema.sql.DialectPostgreSQL;
+import com.google.gwtorm.schema.sql.SqlDialect;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Schema_103 extends SchemaVersion {
+ private static final int MAX_SCAN_SIZE = 1000;
+
+ @Inject
+ Schema_103(Provider<Schema_41> prior) {
+ super(prior);
+ }
+
+ @Override
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException {
+ Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
+ final SqlDialect dialect = ((JdbcSchema) db).getDialect();
+ stmt.execute("CREATE INDEX changes_upgrade103 ON changes (sort_key_desc)");
+
+ List<ToUpdate> changes = new ArrayList<ToUpdate>(MAX_SCAN_SIZE);
+
+ PreparedStatement selectStmt =
+ ((JdbcSchema) db).getConnection().prepareStatement(
+ "SELECT change_id, sort_key FROM changes"
+ + " WHERE sort_key_desc IS NULL OR sort_key_desc=''");
+
+ selectStmt.setMaxRows(MAX_SCAN_SIZE);
+
+ PreparedStatement updateChangeStmt =
+ ((JdbcSchema) db).getConnection().prepareStatement(
+ "UPDATE changes SET sort_key_desc = ? WHERE change_id = ?");
+ PreparedStatement updateApprovalStmt =
+ ((JdbcSchema) db).getConnection().prepareStatement(
+ "UPDATE patch_set_approvals SET change_sort_key_desc = ?"
+ + " WHERE change_id = ?");
+
+ try {
+ while (true) {
+ ResultSet rs = selectStmt.executeQuery();
+ try {
+ while (rs.next() && changes.size() < MAX_SCAN_SIZE) {
+ changes.add(new ToUpdate(rs.getInt(1), rs.getString(2)));
+ }
+ } finally {
+ rs.close();
+ }
+
+ if (changes.isEmpty()) {
+ break;
+ }
+
+ int batchSize = 0;
+ for (ToUpdate u : changes) {
+ String desc = Long.toHexString(-1l - Long.parseLong(u.sortKey, 16));
+
+ updateChangeStmt.setString(1, desc);
+ updateChangeStmt.setInt(2, u.id);
+ updateChangeStmt.addBatch();
+
+ updateApprovalStmt.setString(1, desc);
+ updateApprovalStmt.setInt(2, u.id);
+ updateApprovalStmt.addBatch();
+
+ batchSize++;
+
+ if (batchSize >= 200) {
+ updateChangeStmt.executeBatch();
+ updateApprovalStmt.executeBatch();
+ batchSize = 0;
+ }
+ }
+ if (batchSize > 0) {
+ updateChangeStmt.executeBatch();
+ updateApprovalStmt.executeBatch();
+ }
+
+ changes.clear();
+ }
+
+ if (((JdbcSchema) db).getDialect() instanceof DialectMySQL) {
+ stmt.execute("DROP INDEX changes_upgrade103 ON changes");
+ } else {
+ stmt.execute("DROP INDEX changes_upgrade103");
+ }
+
+ if (dialect instanceof DialectPostgreSQL) {
+ stmt.execute("CREATE INDEX changes_allOpenD ON changes (sort_key_desc) WHERE open = 'Y'");
+ stmt.execute("CREATE INDEX changes_byProjectOpenD ON changes (dest_project_name, sort_key_desc) WHERE open = 'Y'");
+ stmt.execute("CREATE INDEX changes_allClosedD ON changes (status, sort_key_desc) WHERE open = 'N'");
+ stmt.execute("CREATE INDEX patch_set_approvals_closedByUserD ON patch_set_approvals (account_id, change_sort_key_desc) WHERE change_open = 'N'");
+
+ } else {
+ stmt.execute("CREATE INDEX changes_allOpenD ON changes (open, sort_key_desc)");
+ stmt.execute("CREATE INDEX changes_byProjectOpenD ON changes (open, dest_project_name, sort_key_desc)");
+ stmt.execute("CREATE INDEX changes_allClosedD ON changes (open, status, sort_key_desc);");
+ stmt.execute("CREATE INDEX patch_set_approvals_closedByUserD ON patch_set_approvals (change_open, account_id, change_sort_key_desc)");
+ }
+ } finally {
+ stmt.close();
+ updateChangeStmt.close();
+ selectStmt.close();
+ }
+ }
+
+ private static class ToUpdate {
+ int id;
+ String sortKey;
+
+ ToUpdate(int changeId, String sortKey) {
+ this.id = changeId;
+ this.sortKey = sortKey;
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/backup/BackupAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/backup/BackupAccess.java
new file mode 100644
index 0000000..cdba707
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/backup/BackupAccess.java
@@ -0,0 +1,82 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema.backup;
+
+import com.google.gwtorm.client.Key;
+import com.google.gwtorm.client.ResultSet;
+import com.google.gwtorm.nosql.IndexFunction;
+import com.google.gwtorm.nosql.NoSqlAccess;
+import com.google.gwtorm.protobuf.ProtobufCodec;
+
+public abstract class BackupAccess<T, K extends Key<?>> extends
+ NoSqlAccess<T, K> {
+ @SuppressWarnings("unchecked")
+ protected BackupAccess(BackupSchema s) {
+ super(s);
+ }
+
+ @Override
+ public abstract ProtobufCodec<T> getObjectCodec();
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected ResultSet scanIndex(IndexFunction index, byte[] fromKey,
+ byte[] toKey, int limit, boolean order) {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected ResultSet scanPrimaryKey(byte[] fromKey, byte[] toKey, int limit,
+ boolean order) {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void delete(Iterable instances) {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T get(K key) {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void insert(Iterable instances) {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public ResultSet iterateAllEntities() {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void update(Iterable instances) {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void upsert(Iterable instances) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/backup/BackupDatabase.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/backup/BackupDatabase.java
new file mode 100644
index 0000000..d80de7a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/backup/BackupDatabase.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema.backup;
+
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.Schema;
+import com.google.gwtorm.nosql.NoSqlDatabase;
+
+@SuppressWarnings("unchecked")
+public class BackupDatabase<T extends Schema> extends
+ NoSqlDatabase<T, BackupSchema, BackupAccess> {
+ public BackupDatabase(Class<T> schema) throws OrmException {
+ super(BackupSchema.class, BackupAccess.class, schema);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/backup/BackupSchema.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/backup/BackupSchema.java
new file mode 100644
index 0000000..da19212
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/backup/BackupSchema.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema.backup;
+
+import com.google.gwtorm.client.Schema;
+import com.google.gwtorm.nosql.NoSqlSchema;
+
+public abstract class BackupSchema<T extends Schema> extends NoSqlSchema {
+ protected BackupSchema(BackupDatabase<T> d) {
+ super(d);
+ }
+
+ @Override
+ protected long nextLong(String poolName) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void close() {
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/backup/Counters.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/backup/Counters.java
new file mode 100644
index 0000000..6e9b05b
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/backup/Counters.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema.backup;
+
+import com.google.gwtorm.client.Column;
+import com.google.gwtorm.protobuf.CodecFactory;
+import com.google.gwtorm.protobuf.ProtobufCodec;
+
+public class Counters {
+ public static final ProtobufCodec<Counters> CODEC =
+ CodecFactory.encoder(Counters.class);
+
+ @Column(id = 1)
+ public int accountGroupId;
+
+ @Column(id = 2)
+ public int accountId;
+
+ @Column(id = 3)
+ public int changeId;
+
+ @Column(id = 4)
+ public int changeMessageId;
+
+ @Column(id = 5)
+ public int contributorAgreementId;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/backup/RestoreBackup.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/backup/RestoreBackup.java
new file mode 100644
index 0000000..dd03abc
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/backup/RestoreBackup.java
@@ -0,0 +1,151 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema.backup;
+
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gwtorm.client.Access;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.protobuf.ProtobufCodec;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import org.eclipse.jgit.util.IO;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.GZIPInputStream;
+
+/** Restore the database from a compressed series of protobuf objects. */
+public class RestoreBackup {
+ public static void restore(InputStream in, ReviewDb dst) throws IOException,
+ OrmException {
+ in = new BufferedInputStream(new GZIPInputStream(in, 8192));
+
+ BackupDatabase<ReviewDb> bck = new BackupDatabase<ReviewDb>(ReviewDb.class);
+ ReviewDb src = bck.open();
+
+ dst.setAutoFlush(false);
+ restoreImpl(in, src, dst);
+ dst.flush();
+ }
+
+ @SuppressWarnings("unchecked")
+ private static void restoreImpl(InputStream in, ReviewDb src, ReviewDb dst)
+ throws IOException, OrmException {
+ // Remove every row, we're about to overwrite them all.
+ //
+ for (Access<?, ?> s : dst.allRelations()) {
+ List objects = s.iterateAllEntities().toList();
+ s.delete(objects);
+ }
+
+ Map<Integer, BackupAccess<?, ?>> read = index(src);
+ Map<Integer, Access<?, ?>> store = index(dst);
+
+ // The first object should be a Counters.
+ //
+ Counters cnts = Counters.CODEC.decodeWithSize(in);
+
+ // Remaining objects are length delimited until EOF.
+ //
+ Set<Integer> notKnown = new HashSet<Integer>();
+ for (;;) {
+ in.mark(1);
+ if (in.read() == -1) {
+ break;
+ }
+
+ in.reset();
+ in.mark(32);
+ int len = readRawVarint32(in);
+ int id = readRawVarint32(in) >>> 3;
+
+ BackupAccess<?, ?> r = read.get(id);
+ Access<?, ?> w = store.get(id);
+
+ if (r != null && w != null) {
+ in.reset();
+ ProtobufCodec pc = r.getObjectCodec();
+ Set s = Collections.singleton(pc.decodeWithSize(in));
+ w.upsert(s);
+
+ } else {
+ if (notKnown.add(id)) {
+ System.err.println("warning: Skipping relation " + id);
+ }
+ in.reset();
+ if (len != readRawVarint32(in)) {
+ throw new IOException("Stream didn't reset before skipping");
+ }
+ IO.skipFully(in, len);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Map index(ReviewDb src) {
+ Map<Integer, Access<?, ?>> relations = new HashMap<Integer, Access<?, ?>>();
+ for (Access<?, ?> a : src.allRelations()) {
+ relations.put(a.getRelationID(), a);
+ }
+ return relations;
+ }
+
+ private static int readRawVarint32(InputStream in) throws IOException {
+ int b = in.read();
+ if (b == -1) {
+ throw new InvalidProtocolBufferException("Truncated input");
+ }
+
+ if ((b & 0x80) == 0) {
+ return b;
+ }
+
+ int result = b & 0x7f;
+ int offset = 7;
+ for (; offset < 32; offset += 7) {
+ b = in.read();
+ if (b == -1) {
+ throw new InvalidProtocolBufferException("Truncated input");
+ }
+ result |= (b & 0x7f) << offset;
+ if ((b & 0x80) == 0) {
+ return result;
+ }
+ }
+
+ // Keep reading up to 64 bits.
+ for (; offset < 64; offset += 7) {
+ b = in.read();
+ if (b == -1) {
+ throw new InvalidProtocolBufferException("Truncated input");
+ }
+ if ((b & 0x80) == 0) {
+ return result;
+ }
+ }
+
+ throw new InvalidProtocolBufferException("Malformed varint");
+ }
+
+ private RestoreBackup() {
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/NoSshInfo.java
similarity index 61%
copy from gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/ssh/NoSshInfo.java
index 1eb1a4f..882181d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/NoSshInfo.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 The Android Open Source Project
+// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,15 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.account;
+package com.google.gerrit.server.ssh;
-import com.google.gerrit.reviewdb.Account;
+import com.google.gerrit.server.ssh.SshInfo;
-import java.util.Set;
+import com.jcraft.jsch.HostKey;
-/** Translates an email address to a set of matching accounts. */
-public interface AccountByEmailCache {
- public Set<Account.Id> get(String email);
+import java.util.Collections;
+import java.util.List;
- public void evict(String email);
+class NoSshInfo implements SshInfo {
+ @Override
+ public List<HostKey> getHostKeys() {
+ return Collections.emptyList();
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ssh/NoSshKeyCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/NoSshKeyCache.java
new file mode 100644
index 0000000..5917dfc
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/NoSshKeyCache.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.ssh;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.gerrit.common.errors.InvalidSshKeyException;
+import com.google.gerrit.reviewdb.AccountSshKey;
+
+class NoSshKeyCache implements SshKeyCache {
+ @Override
+ public ListenableFuture<Void> evictAsync(String username) {
+ return Futures.immediateFuture(null);
+ }
+
+ @Override
+ public AccountSshKey create(AccountSshKey.Id id, String encoded)
+ throws InvalidSshKeyException {
+ throw new InvalidSshKeyException();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ssh/NoSshModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/NoSshModule.java
new file mode 100644
index 0000000..21b1a54
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/NoSshModule.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.ssh;
+
+import com.google.inject.AbstractModule;
+
+/**
+ * Disables the SSH support by stubbing out relevant objects.
+ */
+public class NoSshModule extends AbstractModule {
+ @Override
+ protected void configure() {
+ bind(SshInfo.class).to(NoSshInfo.class);
+ bind(SshKeyCache.class).to(NoSshKeyCache.class);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshKeyCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshKeyCache.java
index b56405a..9275dbf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshKeyCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshKeyCache.java
@@ -14,12 +14,13 @@
package com.google.gerrit.server.ssh;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.common.errors.InvalidSshKeyException;
import com.google.gerrit.reviewdb.AccountSshKey;
/** Permits controlling the contents of the SSH key cache area. */
public interface SshKeyCache {
- public void evict(String username);
+ public ListenableFuture<Void> evictAsync(String username);
public AccountSshKey create(AccountSshKey.Id id, String encoded)
throws InvalidSshKeyException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/CompoundFuture.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/CompoundFuture.java
new file mode 100644
index 0000000..1d0b91a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/CompoundFuture.java
@@ -0,0 +1,192 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.util;
+
+import com.google.common.util.concurrent.ForwardingListenableFuture;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/** A future that runs all of the futures given to it. */
+public class CompoundFuture<V> extends ForwardingListenableFuture<V> {
+ /**
+ * Construct a new compound future around several futures.
+ *
+ * @param <V> the type of the result this future produces.
+ * @param result the future that will provide the final result. This future
+ * will be waited on last.
+ * @param other1 any other future that should also complete before this future
+ * completes. Their results will be discarded, and thus should be
+ * declared to return Void.
+ * @return a future to wait on several futures.
+ */
+ @SuppressWarnings("unchecked")
+ public static <V> CompoundFuture<V> wrap(ListenableFuture<V> result,
+ Future<Void> other1) {
+ return new CompoundFuture<V>(result, other1);
+ }
+
+ /**
+ * Construct a new compound future around several futures.
+ *
+ * @param <V> the type of the result this future produces.
+ * @param result the future that will provide the final result. This future
+ * will be waited on last.
+ * @param other1 any other future that should also complete before this future
+ * completes. Their results will be discarded, and thus should be
+ * declared to return Void.
+ * @param other2 any other future that should also complete before this future
+ * completes. Their results will be discarded, and thus should be
+ * declared to return Void.
+ * @return a future to wait on several futures.
+ */
+ @SuppressWarnings("unchecked")
+ public static <V> CompoundFuture<V> wrap(ListenableFuture<V> result,
+ Future<Void> other1, Future<Void> other2) {
+ return new CompoundFuture<V>(result, other1, other2);
+ }
+
+ /**
+ * Construct a new compound future around several futures.
+ *
+ * @param <V> the type of the result this future produces.
+ * @param result the future that will provide the final result. This future
+ * will be waited on last.
+ * @param other1 any other future that should also complete before this future
+ * completes. Their results will be discarded, and thus should be
+ * declared to return Void.
+ * @param other2 any other future that should also complete before this future
+ * completes. Their results will be discarded, and thus should be
+ * declared to return Void.
+ * @param other3 any other future that should also complete before this future
+ * completes. Their results will be discarded, and thus should be
+ * declared to return Void.
+ * @return a future to wait on several futures.
+ */
+ @SuppressWarnings("unchecked")
+ public static <V> CompoundFuture<V> wrap(ListenableFuture<V> result,
+ Future<Void> other1, Future<Void> other2, Future<Void> other3) {
+ return new CompoundFuture<V>(result, other1, other2, other3);
+ }
+
+ /**
+ * Construct a new compound future around several futures.
+ *
+ * @param <V> the type of the result this future produces.
+ * @param result the future that will provide the final result. This future
+ * will be waited on last.
+ * @param others any other futures that should also complete before this
+ * future completes. Their results will be discarded, and thus should
+ * be declared to return Void.
+ * @return a future to wait on several futures.
+ */
+ public static <V> CompoundFuture<V> wrap(ListenableFuture<V> result,
+ Future<Void>... others) {
+ Future<Void>[] r = CompoundFuture.<Void> newArray(others.length);
+ System.arraycopy(others, 0, r, 0, others.length);
+ return new CompoundFuture<V>(result, r);
+ }
+
+ /**
+ * Construct a new compound future around several futures.
+ *
+ * @param <V> the type of the result this future produces.
+ * @param result the future that will provide the final result. This future
+ * will be waited on last.
+ * @param others any other futures that should also complete before this
+ * future completes. Their results will be discarded, and thus should
+ * be declared to return Void.
+ * @return a future to wait on several futures.
+ */
+ public static <V> CompoundFuture<V> wrap(ListenableFuture<V> result,
+ Collection<Future<Void>> others) {
+ Future<Void>[] r = CompoundFuture.<Void> newArray(others.size());
+ return new CompoundFuture<V>(result, others.toArray(r));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> Future<T>[] newArray(int sz) {
+ return new Future[sz];
+ }
+
+ private final ListenableFuture<V> result;
+ private final Future<Void>[] others;
+
+ private CompoundFuture(ListenableFuture<V> result, Future<Void>... others) {
+ this.result = result;
+ this.others = others;
+ }
+
+ @Override
+ protected ListenableFuture<V> delegate() {
+ return result;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ boolean res = super.cancel(mayInterruptIfRunning);
+ for (Future<Void> f : others) {
+ f.cancel(mayInterruptIfRunning);
+ }
+ return res;
+ }
+
+ @Override
+ public V get() throws InterruptedException, ExecutionException {
+ for (Future<Void> f : others) {
+ if (!f.isDone()) {
+ f.get();
+ }
+ }
+ return super.get();
+ }
+
+ @Override
+ public V get(long timeout, TimeUnit unit) throws InterruptedException,
+ ExecutionException, TimeoutException {
+ if (unit != TimeUnit.MILLISECONDS) {
+ timeout = TimeUnit.MILLISECONDS.convert(timeout, unit);
+ unit = TimeUnit.MILLISECONDS;
+ }
+
+ for (Future<Void> f : others) {
+ if (!f.isDone()) {
+ long start = System.currentTimeMillis();
+ f.get(timeout, unit);
+
+ timeout -= Math.max(0, System.currentTimeMillis() - start);
+ if (timeout <= 0) {
+ throw new TimeoutException();
+ }
+ }
+ }
+
+ return super.get(timeout, unit);
+ }
+
+ @Override
+ public boolean isDone() {
+ for (Future<Void> f : others) {
+ if (!f.isDone()) {
+ return false;
+ }
+ }
+ return super.isDone();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/ConcatFuture.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/ConcatFuture.java
new file mode 100644
index 0000000..9bacec0
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/ConcatFuture.java
@@ -0,0 +1,53 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.util;
+
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.AbstractListenableFuture;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+class ConcatFuture<V> extends AbstractListenableFuture<List<V>> {
+ private final AtomicInteger pending;
+ private final List<ListenableFuture<List<V>>> sources;
+
+ ConcatFuture(List<ListenableFuture<List<V>>> all) {
+ sources = all;
+ pending = new AtomicInteger(sources.size());
+
+ Runnable done = new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ };
+ for (ListenableFuture<List<V>> future : sources) {
+ future.addListener(done, MoreExecutors.sameThreadExecutor());
+ }
+ }
+
+ private void finish() {
+ if (pending.decrementAndGet() == 0) {
+ List<V> all = Lists.newArrayList();
+ for (ListenableFuture<List<V>> f : sources) {
+ all.addAll(FutureUtil.get(f));
+ }
+ set(all);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/FutureException.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/FutureException.java
new file mode 100644
index 0000000..9534bec
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/FutureException.java
@@ -0,0 +1,10 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+
+package com.google.gerrit.server.util;
+
+/** Exception thrown by {@link FutureUtil#get(java.util.concurrent.Future)}. */
+public class FutureException extends RuntimeException {
+ FutureException(Throwable why) {
+ super("Future computation failed", why);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/FutureUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/FutureUtil.java
new file mode 100644
index 0000000..6a01b4d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/FutureUtil.java
@@ -0,0 +1,221 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.util;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/** Utilities to work with futures. */
+public class FutureUtil {
+ private static final Logger log = LoggerFactory.getLogger(FutureUtil.class);
+
+ /**
+ * Construct a future that concatenates the resulting lists.
+ *
+ * @param <V> type of the list element.
+ * @param all the futures whose results will be concatenated.
+ * @return a future to get all results.
+ */
+ public static <V> ListenableFuture<List<V>> concat(
+ List<ListenableFuture<List<V>>> all) {
+ return new ConcatFuture<V>(all);
+ }
+
+ /**
+ * Construct a future that concatenates the resulting lists.
+ *
+ * @param <V> type of the list element.
+ * @param all the futures whose results will be concatenated.
+ * @return a future to get all results.
+ */
+ public static <V> ListenableFuture<List<V>> concatSingletons(
+ List<ListenableFuture<V>> all) {
+ Function<V, List<V>> asList = new Function<V, List<V>>() {
+ public List<V> apply(V item) {
+ if (item != null) {
+ return Collections.singletonList(item);
+ } else {
+ return Collections.<V> emptyList();
+ }
+ }
+ };
+
+ List<ListenableFuture<List<V>>> r = Lists.newArrayList();
+ for (ListenableFuture<V> f : all) {
+ r.add(Futures.compose(f, asList));
+ }
+ return new ConcatFuture<V>(r);
+ }
+
+ /**
+ * Get the future's value and return it to the caller.
+ *
+ * If the method is interrupted during computation, or the future throws an
+ * exception this is wrapped into an {@link FutureException} and rethrown.
+ *
+ * @param <V> the return type of the future.
+ * @param future the future itself
+ * @return the value of the future.
+ * @throws FutureException if the future's get method threw an exception.
+ */
+ public static <V> V get(Future<V> future) {
+ try {
+ return future.get();
+ } catch (InterruptedException e) {
+ throw new FutureException(e);
+ } catch (ExecutionException e) {
+ throw new FutureException(e);
+ }
+ }
+
+ /**
+ * Get the future's value and return it to the caller.
+ *
+ * If the method is interrupted during computation, or the future throws an
+ * exception this is wrapped into an {@link FutureException} and rethrown.
+ *
+ * @param <V> the return type of the future.
+ * @param future the future itself
+ * @return the value of the future; null if the future returned null or it
+ * threw an exception during completion.
+ */
+ public static <V> V getOrNull(Future<V> future) {
+ try {
+ return future.get();
+
+ } catch (InterruptedException e) {
+ log.warn("Interrupted while waiting on future, using empty result", e);
+ return null;
+
+ } catch (ExecutionException e) {
+ log.warn("Interrupted while waiting on future, using empty result", e);
+ return null;
+ }
+ }
+
+ /**
+ * Get the future's value and return it to the caller.
+ *
+ * If the method is interrupted during computation, or the future throws an
+ * exception, the event is logged and an empty collection is returned.
+ *
+ * @param <V> the return type of the future.
+ * @param future the future itself
+ * @return the value of the future.
+ */
+ @SuppressWarnings("unchecked")
+ public static <V> List<V> getOrEmptyList(Future<List<V>> future) {
+ try {
+ return future.get();
+
+ } catch (InterruptedException e) {
+ log.warn("Interrupted while waiting on future, using empty result", e);
+ return Lists.newArrayListWithCapacity(0);
+
+ } catch (ExecutionException e) {
+ log.warn("Error in future collection, using empty result", e);
+ return Lists.newArrayListWithCapacity(0);
+ }
+ }
+
+ /**
+ * Get the future's value and return it to the caller.
+ *
+ * If the method is interrupted during computation, or the future throws an
+ * exception, the event is logged and an empty collection is returned.
+ *
+ * @param <V> the return type of the future.
+ * @param future the future itself
+ * @return the value of the future.
+ */
+ @SuppressWarnings("unchecked")
+ public static <V> Set<V> getOrEmptySet(Future<Set<V>> future) {
+ try {
+ return future.get();
+
+ } catch (InterruptedException e) {
+ log.warn("Interrupted while waiting on future, using empty result", e);
+ return Sets.newHashSetWithExpectedSize(0);
+
+ } catch (ExecutionException e) {
+ log.warn("Error in future collection, using empty result", e);
+ return Sets.newHashSetWithExpectedSize(0);
+ }
+ }
+
+ /**
+ * Flatten a map of futures down to actual values.
+ *
+ * @param <K> type of the map entry key.
+ * @param <V> type of the map entry value.
+ * @param want the map of futures to resolve.
+ * @return the resulting map.
+ */
+ public static <K, V> Map<K, V> getMap(Map<K, Future<V>> want) {
+ Map<K, V> res = Maps.newHashMapWithExpectedSize(want.size());
+ for (Map.Entry<K, Future<V>> ent : want.entrySet()) {
+ res.put(ent.getKey(), get(ent.getValue()));
+ }
+ return res;
+ }
+
+ /**
+ * Wait for a future to complete, and discard its results.
+ *
+ * @param future the future to wait for completion of.
+ */
+ public static void waitFor(Future<Void> future) {
+ get(future);
+ }
+
+ /**
+ * Wait for multiple futures to complete, and discard all results.
+ *
+ * @param futures the futures to wait for completion of.
+ */
+ public static void waitFor(Future<Void>... futures) {
+ for (Future<Void> f : futures) {
+ waitFor(f);
+ }
+ }
+
+ /**
+ * Wait for multiple futures to complete, and discard all results.
+ *
+ * @param futures the futures to wait for completion of.
+ */
+ public static void waitFor(Iterable<? extends Future<Void>> futures) {
+ for (Future<Void> f : futures) {
+ waitFor(f);
+ }
+ }
+
+ private FutureUtil() {
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java
index 36a52e2..61664f2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java
@@ -31,6 +31,7 @@
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefControl;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -68,7 +69,7 @@
this.userFactory = userFactory;
change = c;
- project = projectCache.get(change.getProject());
+ project = FutureUtil.get(projectCache.get(change.getProject()));
for (final PatchSetApproval ca : all) {
if (psId.equals(ca.getPatchSetId())) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/ChangeUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/ChangeUtilTest.java
new file mode 100644
index 0000000..dcc6d2f
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/ChangeUtilTest.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server;
+
+import junit.framework.TestCase;
+
+public class ChangeUtilTest extends TestCase {
+
+ public void testInvertSortKey() {
+ assertEquals("ffffffffffffffff", ChangeUtil
+ .invertSortKey("0000000000000000"));
+
+ assertEquals("0000000000000001", ChangeUtil
+ .invertSortKey("fffffffffffffffe"));
+
+ assertEquals("0001600000000000", ChangeUtil
+ .invertSortKey("fffe9fffffffffff"));
+
+ assertEquals("/", ChangeUtil.invertSortKey("z"));
+
+ assertEquals("z", ChangeUtil.invertSortKey("/"));
+ }
+
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
index f819dac..11f6de6 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
@@ -20,6 +20,7 @@
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
+import com.google.common.util.concurrent.Futures;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.AccountGroup;
@@ -262,24 +263,22 @@
}
private Account.Id user(final String name, final String email) {
- final AccountState s = makeUser(name, email);
- expect(accountCache.get(eq(s.getAccount().getId()))).andReturn(s);
- return s.getAccount().getId();
+ final Account s = makeUser(name, email);
+ expect(accountCache.getAccount(eq(s.getId()))) //
+ .andReturn(Futures.immediateFuture(s));
+ return s.getId();
}
private Account.Id userNoLookup(final String name, final String email) {
- final AccountState s = makeUser(name, email);
- return s.getAccount().getId();
+ final Account s = makeUser(name, email);
+ return s.getId();
}
- private AccountState makeUser(final String name, final String email) {
+ private Account makeUser(final String name, final String email) {
final Account.Id userId = new Account.Id(42);
final Account account = new Account(userId);
account.setFullName(name);
account.setPreferredEmail(email);
- final AccountState s =
- new AccountState(account, Collections.<AccountGroup.Id> emptySet(),
- Collections.<AccountExternalId> emptySet());
- return s;
+ return account;
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/NoSqlSchemaCreatorTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/NoSqlSchemaCreatorTest.java
new file mode 100644
index 0000000..6cb3d16
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/NoSqlSchemaCreatorTest.java
@@ -0,0 +1,94 @@
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.schema;
+
+import com.google.gerrit.reviewdb.CurrentSchemaVersion;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.reviewdb.SystemConfig;
+import com.google.gerrit.server.config.SystemConfigProvider;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.nosql.heap.MemoryDatabase;
+import com.google.inject.Guice;
+import com.google.inject.Key;
+import com.google.inject.Provider;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+
+public class NoSqlSchemaCreatorTest extends TestCase {
+ private MemoryDatabase<ReviewDb> db;
+ private SchemaVersion schemaVersion;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ db = new MemoryDatabase<ReviewDb>(ReviewDb.class);
+ schemaVersion =
+ Guice.createInjector(new SchemaVersion.Module()).getBinding(
+ Key.get(SchemaVersion.class, Current.class)).getProvider().get();
+ }
+
+ private CurrentSchemaVersion getSchemaVersion() throws OrmException {
+ final ReviewDb c = db.open();
+ try {
+ return c.schemaVersion().get(new CurrentSchemaVersion.Key());
+ } finally {
+ c.close();
+ }
+ }
+
+ private void assertSchemaVersion() throws OrmException {
+ final CurrentSchemaVersion act = getSchemaVersion();
+ TestCase.assertEquals(schemaVersion.getVersionNbr(), act.versionNbr);
+ }
+
+ private SystemConfig getSystemConfig() {
+ return new SystemConfigProvider(db, new Provider<SchemaVersion>() {
+ public SchemaVersion get() {
+ return schemaVersion;
+ }
+ }).get();
+ }
+
+ public void testCreateSchema() throws OrmException {
+ final ReviewDb c = db.open();
+ try {
+ new SchemaCreator(new File("."), schemaVersion).create(c);
+ } finally {
+ c.close();
+ }
+
+ assertSchemaVersion();
+ final SystemConfig config = getSystemConfig();
+ assertNotNull(config);
+ assertNotNull(config.adminGroupId);
+ assertNotNull(config.anonymousGroupId);
+ assertNotNull(config.registeredGroupId);
+
+ // By default sitePath is set to the current working directory.
+ //
+ File sitePath = new File(".").getAbsoluteFile();
+ if (sitePath.getName().equals(".")) {
+ sitePath = sitePath.getParentFile();
+ }
+ assertEquals(sitePath.getAbsolutePath(), config.sitePath);
+
+ // This is randomly generated and should be at least 20 bytes long.
+ //
+ assertNotNull(config.registerEmailPrivateKey);
+ assertTrue(20 < config.registerEmailPrivateKey.length());
+ }
+}
diff --git a/gerrit-sshd/pom.xml b/gerrit-sshd/pom.xml
index 32bcf57..d48972f 100644
--- a/gerrit-sshd/pom.xml
+++ b/gerrit-sshd/pom.xml
@@ -64,5 +64,11 @@
<artifactId>gerrit-server</artifactId>
<version>${project.version}</version>
</dependency>
+
+ <dependency>
+ <groupId>com.google.gerrit</groupId>
+ <artifactId>gerrit-ehcache</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
</project>
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
index 977d209..e2a37f5 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
@@ -107,13 +107,13 @@
}
}
- final Iterable<SshKeyCacheEntry> keyList = sshKeyCache.get(username);
- final SshKeyCacheEntry key = find(keyList, suppliedKey);
+ final SshKeyCacheImpl.EntryList keyList = sshKeyCache.get(username);
+ final SshKeyCacheEntry key = find(keyList.getKeys(), suppliedKey);
if (key == null) {
final String err;
- if (keyList == SshKeyCacheImpl.NO_SUCH_USER) {
+ if (keyList.getType() == SshKeyCacheImpl.EntryList.Type.NO_SUCH_USER) {
err = "user-not-found";
- } else if (keyList == SshKeyCacheImpl.NO_KEYS) {
+ } else if (keyList.getType() == SshKeyCacheImpl.EntryList.Type.NO_KEYS) {
err = "key-list-empty";
} else {
err = "no-matching-key";
@@ -128,7 +128,7 @@
// security check to ensure there aren't two users sharing the same
// user name on the server.
//
- for (final SshKeyCacheEntry otherKey : keyList) {
+ for (SshKeyCacheEntry otherKey : keyList.getKeys()) {
if (!key.getAccount().equals(otherKey.getAccount())) {
sd.authenticationError(username, "keys-cross-accounts");
return false;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
index 2636ff2..b7c0761 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
@@ -185,7 +185,7 @@
@Override
public synchronized void start() {
- if (acceptor == null) {
+ if (acceptor == null && !listen.isEmpty()) {
checkConfig();
acceptor = createAcceptor();
@@ -226,6 +226,10 @@
}
private List<HostKey> computeHostKeys() {
+ if (listen.isEmpty()) {
+ return Collections.emptyList();
+ }
+
final List<PublicKey> keys = myHostKeys();
final ArrayList<HostKey> r = new ArrayList<HostKey>();
for (final PublicKey pub : keys) {
@@ -296,6 +300,10 @@
return bind;
}
+ if (want.length == 1 && isOff(want[0])) {
+ return bind;
+ }
+
for (final String desc : want) {
try {
bind.add(SocketUtil.resolve(desc, DEFAULT_PORT));
@@ -306,6 +314,12 @@
return bind;
}
+ private static boolean isOff(String listenHostname) {
+ return "off".equalsIgnoreCase(listenHostname)
+ || "none".equalsIgnoreCase(listenHostname)
+ || "no".equalsIgnoreCase(listenHostname);
+ }
+
@SuppressWarnings("unchecked")
private void initProviderBouncyCastle() {
setKeyExchangeFactories(Arrays.<NamedFactory<KeyExchange>> asList(
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheEntry.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheEntry.java
index 81e019e..cded3dc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheEntry.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheEntry.java
@@ -16,16 +16,45 @@
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountSshKey;
+import com.google.gwtorm.client.Column;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
class SshKeyCacheEntry {
- private final AccountSshKey.Id id;
- private final PublicKey publicKey;
+ private final static PublicKey INVALID_KEY = new PublicKey() {
+ @Override
+ public String getAlgorithm() {
+ throw new UnsupportedOperationException();
+ }
- SshKeyCacheEntry(final AccountSshKey.Id i, final PublicKey k) {
+ @Override
+ public byte[] getEncoded() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getFormat() {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ @Column(id = 1)
+ protected AccountSshKey.Id id;
+
+ @Column(id = 2)
+ protected AccountSshKey key;
+
+ private transient volatile PublicKey publicKey;
+
+ SshKeyCacheEntry(final AccountSshKey.Id i, final AccountSshKey k)
+ throws NoSuchAlgorithmException, InvalidKeySpecException,
+ NoSuchProviderException {
id = i;
- publicKey = k;
+ key = k;
+ publicKey = SshUtil.parse(k);
}
Account.Id getAccount() {
@@ -33,6 +62,18 @@
}
boolean match(final PublicKey inkey) {
+ if (publicKey == null) {
+ try {
+ publicKey = SshUtil.parse(key);
+ } catch (OutOfMemoryError e) {
+ // This is the only case where we assume the problem has nothing
+ // to do with the key object, and instead we must abort this load.
+ //
+ throw e;
+ } catch (Throwable e) {
+ publicKey = INVALID_KEY;
+ }
+ }
return publicKey.equals(inkey);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
index c5f64f7..fb4bcef 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
@@ -14,16 +14,21 @@
package com.google.gerrit.sshd;
-import static com.google.gerrit.reviewdb.AccountExternalId.SCHEME_USERNAME;
-
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.common.errors.InvalidSshKeyException;
+import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.AccountSshKey;
import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.EntryCreator;
import com.google.gerrit.server.ssh.SshKeyCache;
+import com.google.gerrit.server.util.FutureUtil;
+import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
@@ -38,8 +43,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidKeySpecException;
-import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -50,49 +54,44 @@
LoggerFactory.getLogger(SshKeyCacheImpl.class);
private static final String CACHE_NAME = "sshkeys";
- static final Iterable<SshKeyCacheEntry> NO_SUCH_USER = none();
- static final Iterable<SshKeyCacheEntry> NO_KEYS = none();
-
public static Module module() {
return new CacheModule() {
@Override
protected void configure() {
- final TypeLiteral<Cache<String, Iterable<SshKeyCacheEntry>>> type =
- new TypeLiteral<Cache<String, Iterable<SshKeyCacheEntry>>>() {};
- core(type, CACHE_NAME).populateWith(Loader.class);
+ final TypeLiteral<Cache<Username, EntryList>> type =
+ new TypeLiteral<Cache<Username, EntryList>>() {};
+ cache(type, CACHE_NAME).populateWith(Loader.class);
bind(SshKeyCacheImpl.class);
bind(SshKeyCache.class).to(SshKeyCacheImpl.class);
}
};
}
- private static Iterable<SshKeyCacheEntry> none() {
- return Collections.unmodifiableCollection(Arrays
- .asList(new SshKeyCacheEntry[0]));
- }
-
- private final Cache<String, Iterable<SshKeyCacheEntry>> cache;
+ private final Cache<Username, EntryList> cache;
@Inject
- SshKeyCacheImpl(
- @Named(CACHE_NAME) final Cache<String, Iterable<SshKeyCacheEntry>> cache) {
+ SshKeyCacheImpl(@Named(CACHE_NAME) final Cache<Username, EntryList> cache) {
this.cache = cache;
}
- public Iterable<SshKeyCacheEntry> get(String username) {
- return cache.get(username);
+ EntryList get(String username) {
+ return FutureUtil.get(cache.get(new Username(username)));
}
- public void evict(String username) {
- cache.remove(username);
+ public ListenableFuture<Void> evictAsync(String username) {
+ if (username != null) {
+ return cache.removeAsync(new Username(username));
+ } else {
+ return Futures.immediateFuture(null);
+ }
}
@Override
public AccountSshKey create(AccountSshKey.Id id, String encoded)
throws InvalidSshKeyException {
try {
- final AccountSshKey key =
- new AccountSshKey(id, SshUtil.toOpenSshPublicKey(encoded));
+ encoded = SshUtil.toOpenSshPublicKey(encoded);
+ AccountSshKey key = new AccountSshKey(id, encoded);
SshUtil.parse(key);
return key;
} catch (NoSuchAlgorithmException e) {
@@ -107,50 +106,97 @@
}
}
- static class Loader extends EntryCreator<String, Iterable<SshKeyCacheEntry>> {
+ static class Username {
+ @Column(id = 1)
+ String name;
+
+ Username() {
+ }
+
+ Username(String name) {
+ this.name = name;
+ }
+ }
+
+ static class EntryList {
+ static enum Type {
+ VALID_HAS_KEYS, INVALID_USER, NO_SUCH_USER, NO_KEYS
+ }
+
+ @Column(id = 1)
+ Type type;
+
+ @Column(id = 2)
+ Collection<SshKeyCacheEntry> keys;
+
+ EntryList() {
+ type = Type.NO_KEYS;
+ keys = Collections.emptyList();
+ }
+
+ EntryList(Type t, Collection<SshKeyCacheEntry> k) {
+ this.type = t;
+ this.keys = k;
+ }
+
+ Collection<SshKeyCacheEntry> getKeys() {
+ return keys;
+ }
+
+ Type getType() {
+ return type;
+ }
+ }
+
+ static class Loader extends EntryCreator<Username, EntryList> {
private final SchemaFactory<ReviewDb> schema;
+ private final AccountCache accountCache;
@Inject
- Loader(SchemaFactory<ReviewDb> schema) {
+ Loader(SchemaFactory<ReviewDb> schema, AccountCache accountCache) {
this.schema = schema;
+ this.accountCache = accountCache;
}
@Override
- public Iterable<SshKeyCacheEntry> createEntry(String username)
- throws Exception {
+ public EntryList createEntry(Username username) throws Exception {
+ AccountExternalId user = FutureUtil.get(accountCache.get( //
+ AccountExternalId.forUsername(username.name)));
+ if (user == null) {
+ Collection<SshKeyCacheEntry> none = Collections.emptyList();
+ return new EntryList(EntryList.Type.NO_SUCH_USER, none);
+ }
+
+ final Account.Id accountId = user.getAccountId();
final ReviewDb db = schema.open();
try {
- final AccountExternalId.Key key =
- new AccountExternalId.Key(SCHEME_USERNAME, username);
- final AccountExternalId user = db.accountExternalIds().get(key);
- if (user == null) {
- return NO_SUCH_USER;
- }
-
- final List<SshKeyCacheEntry> kl = new ArrayList<SshKeyCacheEntry>(4);
- for (AccountSshKey k : db.accountSshKeys().byAccount(
- user.getAccountId())) {
+ List<SshKeyCacheEntry> kl = Lists.newArrayListWithExpectedSize(4);
+ for (AccountSshKey k : db.accountSshKeys().byAccount(accountId)) {
if (k.isValid()) {
add(db, kl, k);
}
}
if (kl.isEmpty()) {
- return NO_KEYS;
+ Collection<SshKeyCacheEntry> none = Collections.emptyList();
+ return new EntryList(EntryList.Type.NO_KEYS, none);
+ } else {
+ kl = Collections.unmodifiableList(kl);
+ return new EntryList(EntryList.Type.VALID_HAS_KEYS, kl);
}
- return Collections.unmodifiableList(kl);
} finally {
db.close();
}
}
@Override
- public Iterable<SshKeyCacheEntry> missing(String username) {
- return Collections.emptyList();
+ public EntryList missing(Username username) {
+ Collection<SshKeyCacheEntry> none = Collections.emptyList();
+ return new EntryList(EntryList.Type.INVALID_USER, none);
}
private void add(ReviewDb db, List<SshKeyCacheEntry> kl, AccountSshKey k) {
try {
- kl.add(new SshKeyCacheEntry(k.getKey(), SshUtil.parse(k)));
+ kl.add(new SshKeyCacheEntry(k.getKey(), k));
} catch (OutOfMemoryError e) {
// This is the only case where we assume the problem has nothing
// to do with the key object, and instead we must abort this load.
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountGroupIdHandler.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountGroupIdHandler.java
index 13ff1b1..9ac0465 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountGroupIdHandler.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/AccountGroupIdHandler.java
@@ -16,6 +16,7 @@
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -42,7 +43,7 @@
public final int parseArguments(final Parameters params)
throws CmdLineException {
final String n = params.getParameter(0);
- final AccountGroup group = groupCache.get(new AccountGroup.NameKey(n));
+ final AccountGroup group = get(n);
if (group == null) {
throw new CmdLineException(owner, "Group \"" + n + "\" does not exist");
}
@@ -50,6 +51,10 @@
return 1;
}
+ private AccountGroup get(String name) {
+ return FutureUtil.getOrNull(groupCache.get(new AccountGroup.NameKey(name)));
+ }
+
@Override
public final String getDefaultMetaVariable() {
return "GROUP";
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateAccount.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateAccount.java
index 06e92ca..8744cfa 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateAccount.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateAccount.java
@@ -14,6 +14,7 @@
package com.google.gerrit.sshd.commands;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.gerrit.common.errors.InvalidSshKeyException;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
@@ -23,14 +24,15 @@
import com.google.gerrit.reviewdb.AccountSshKey;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountByEmailCache;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.ssh.SshKeyCache;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gerrit.sshd.AdminCommand;
import com.google.gerrit.sshd.BaseCommand;
import com.google.gwtorm.client.OrmDuplicateKeyException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
+import com.google.inject.internal.Lists;
import org.apache.sshd.server.Environment;
import org.kohsuke.args4j.Argument;
@@ -75,9 +77,6 @@
@Inject
private AccountCache accountCache;
- @Inject
- private AccountByEmailCache byEmailCache;
-
@Override
public void start(final Environment env) {
startThread(new CommandRunnable() {
@@ -100,32 +99,39 @@
final AccountSshKey key = readSshKey(id);
AccountExternalId extUser =
- new AccountExternalId(id, new AccountExternalId.Key(
- AccountExternalId.SCHEME_USERNAME, username));
+ new AccountExternalId(id, AccountExternalId.forUsername(username));
- if (db.accountExternalIds().get(extUser.getKey()) != null) {
+ if (FutureUtil.get(accountCache.get(extUser.getKey())) != null) {
throw die("username '" + username + "' already exists");
}
- if (email != null && db.accountExternalIds().get(getEmailKey()) != null) {
+ if (email != null && FutureUtil.get(accountCache.byEmail(email)) != null) {
throw die("email '" + email + "' already exists");
}
+ List<ListenableFuture<Void>> evictions = Lists.newArrayList();
try {
db.accountExternalIds().insert(Collections.singleton(extUser));
+ evictions.add(accountCache.evictAsync(extUser.getKey()));
} catch (OrmDuplicateKeyException duplicateKey) {
+ FutureUtil.waitFor(evictions);
throw die("username '" + username + "' already exists");
}
if (email != null) {
- AccountExternalId extMailto = new AccountExternalId(id, getEmailKey());
+ AccountExternalId extMailto =
+ new AccountExternalId(id, new AccountExternalId.Key(
+ AccountExternalId.SCHEME_MAILTO, email));
extMailto.setEmailAddress(email);
try {
db.accountExternalIds().insert(Collections.singleton(extMailto));
+ evictions.add(accountCache.evictAsync(extMailto.getKey()));
} catch (OrmDuplicateKeyException duplicateKey) {
try {
db.accountExternalIds().delete(Collections.singleton(extUser));
+ evictions.add(accountCache.evictAsync(extUser.getKey()));
} catch (OrmException cleanupError) {
}
+ FutureUtil.waitFor(evictions);
throw die("email '" + email + "' already exists");
}
}
@@ -147,13 +153,10 @@
db.accountGroupMembers().insert(Collections.singleton(m));
}
- sshKeyCache.evict(username);
- accountCache.evictByUsername(username);
- byEmailCache.evict(email);
- }
-
- private AccountExternalId.Key getEmailKey() {
- return new AccountExternalId.Key(AccountExternalId.SCHEME_MAILTO, email);
+ evictions.add(sshKeyCache.evictAsync(username));
+ evictions.add(accountCache.evictAsync(extUser.getKey()));
+ evictions.add(accountCache.evictEmailAsync(email));
+ FutureUtil.waitFor(evictions);
}
private AccountSshKey readSshKey(final Account.Id id)
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminReplicate.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminReplicate.java
index 8e2c74f..6db74c4 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminReplicate.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminReplicate.java
@@ -18,6 +18,7 @@
import com.google.gerrit.server.git.PushAllProjectsOp;
import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gerrit.sshd.AdminCommand;
import com.google.gerrit.sshd.BaseCommand;
import com.google.inject.Inject;
@@ -75,9 +76,9 @@
pushAllOpFactory.create(urlMatch).start(0, TimeUnit.SECONDS);
} else {
- for (final String name : projectNames) {
- final Project.NameKey key = new Project.NameKey(name);
- if (projectCache.get(key) != null) {
+ for (String name : projectNames) {
+ Project.NameKey key = new Project.NameKey(name);
+ if (FutureUtil.getOrNull(projectCache.get(key)) != null) {
replication.scheduleFullSync(key, urlMatch);
} else {
throw new Failure(1, "error: '" + name + "': not a Gerrit project");
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
index bf82a42..e9adbc5 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
@@ -20,6 +20,7 @@
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gerrit.sshd.AdminCommand;
import com.google.gerrit.sshd.BaseCommand;
import com.google.gwtorm.client.OrmException;
@@ -79,7 +80,7 @@
//
Project.NameKey gp = newParent.getProject().getParent();
while (gp != null && grandParents.add(gp)) {
- final ProjectState s = projectCache.get(gp);
+ ProjectState s = FutureUtil.get(projectCache.get(gp));
if (s != null) {
gp = s.getProject().getParent();
} else {
@@ -126,7 +127,7 @@
// Invalidate all projects in cache since inherited rights were changed.
//
- projectCache.evictAll();
+ FutureUtil.waitFor(projectCache.evictAllAsync());
if (err.length() > 0) {
while (err.charAt(err.length() - 1) == '\n') {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java
index 313b9dc..083759c 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java
@@ -14,7 +14,7 @@
package com.google.gerrit.sshd.commands;
-import com.google.gerrit.server.cache.CachePool;
+import com.google.gerrit.ehcache.EhcachePoolImpl;
import com.google.gerrit.sshd.BaseCommand;
import com.google.inject.Inject;
@@ -27,7 +27,7 @@
abstract class CacheCommand extends BaseCommand {
@Inject
- protected CachePool cachePool;
+ protected EhcachePoolImpl cachePool;
protected SortedSet<String> cacheNames() {
final SortedSet<String> names = new TreeSet<String>();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java
index 7618432..f0833a1 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListProjects.java
@@ -14,6 +14,7 @@
package com.google.gerrit.sshd.commands;
+import com.google.common.collect.Lists;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
@@ -22,6 +23,7 @@
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gerrit.sshd.BaseCommand;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
@@ -36,6 +38,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
+import java.util.concurrent.Future;
final class ListProjects extends BaseCommand {
private static final String NODE_PREFIX = "|-- ";
@@ -93,24 +96,27 @@
}
try {
- for (final Project p : db.projects().all()) {
+ List<Future<ProjectState>> want = Lists.newArrayList();
+ for (Project p : db.projects().all()) {
if (p.getNameKey().equals(wildProject)) {
// This project "doesn't exist". At least not as a repository.
//
- continue;
+ } else {
+ want.add(projectCache.get(p.getNameKey()));
}
+ }
- final ProjectState e = projectCache.get(p.getNameKey());
+ for (Future<ProjectState> fEnt : want) {
+ final ProjectState e = FutureUtil.getOrNull(fEnt);
if (e == null) {
// If we can't get it from the cache, pretend its not present.
//
continue;
}
+ final Project p = e.getProject();
final ProjectControl pctl = e.controlFor(currentUser);
-
if (!showTree) {
-
if (!pctl.isVisible()) {
// Require the project itself to be visible to the user.
//
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
index e68c747..148d48e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
@@ -79,16 +79,20 @@
try {
db = dbFactory.open();
try {
- connection = ((JdbcSchema) db).getConnection();
- connection.setAutoCommit(true);
+ if (db instanceof JdbcSchema) {
+ connection = ((JdbcSchema) db).getConnection();
+ connection.setAutoCommit(true);
- statement = connection.createStatement();
- try {
- showBanner();
- readEvalPrintLoop();
- } finally {
- statement.close();
- statement = null;
+ statement = connection.createStatement();
+ try {
+ showBanner();
+ readEvalPrintLoop();
+ } finally {
+ statement.close();
+ statement = null;
+ }
+ } else {
+ out.println("fatal: Backend database is not SQL; aborting.");
}
} finally {
db.close();
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
index a196a3e..e3a0170 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
@@ -21,6 +21,7 @@
import com.google.gerrit.server.git.WorkQueue.Task;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.util.FutureUtil;
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.sshd.AdminHighPriorityCommand;
import com.google.gerrit.sshd.BaseCommand;
@@ -146,7 +147,7 @@
ProjectState e = null;
if (projectName != null) {
- e = projectCache.get(projectName);
+ e = FutureUtil.getOrNull(projectCache.get(projectName));
}
regularUserCanSee = e != null && e.controlFor(userProvider).isVisible();
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/ReviewDbDataSourceProvider.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/ReviewDbDataSourceProvider.java
index 233d53d..50f1507 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/ReviewDbDataSourceProvider.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/ReviewDbDataSourceProvider.java
@@ -15,6 +15,10 @@
package com.google.gerrit.httpd;
import com.google.gerrit.lifecycle.LifecycleListener;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.SchemaFactory;
+import com.google.gwtorm.jdbc.Database;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.Singleton;
@@ -25,14 +29,22 @@
/** Provides access to the {@code ReviewDb} DataSource. */
@Singleton
-final class ReviewDbDataSourceProvider implements Provider<DataSource>,
- LifecycleListener {
- private DataSource ds;
+final class ReviewDbDataSourceProvider implements
+ Provider<SchemaFactory<ReviewDb>>, LifecycleListener {
+ private SchemaFactory<ReviewDb> ds;
+ private DataSource dataSource;
@Override
- public synchronized DataSource get() {
+ public synchronized SchemaFactory<ReviewDb> get() {
+ if (dataSource == null) {
+ dataSource = open();
+ }
if (ds == null) {
- ds = open();
+ try {
+ ds = new Database<ReviewDb>(dataSource, ReviewDb.class);
+ } catch (OrmException err) {
+ throw new ProvisionException("Cannot initialize database", err);
+ }
}
return ds;
}
@@ -43,8 +55,8 @@
@Override
public synchronized void stop() {
- if (ds != null) {
- closeDataSource(ds);
+ if (dataSource != null) {
+ closeDataSource(dataSource);
}
}
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 19c16ca..06ba82d 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -17,8 +17,10 @@
import static com.google.inject.Scopes.SINGLETON;
import static com.google.inject.Stage.PRODUCTION;
+import com.google.gerrit.ehcache.EhcachePoolImpl;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.config.AuthConfigModule;
import com.google.gerrit.server.config.CanonicalWebUrlModule;
import com.google.gerrit.server.config.GerritGlobalModule;
@@ -26,18 +28,20 @@
import com.google.gerrit.server.config.MasterNodeStartup;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePathFromSystemConfigProvider;
+import com.google.gerrit.server.git.LocalDiskRepositoryManager;
+import com.google.gerrit.server.mail.SmtpEmailSender;
import com.google.gerrit.server.schema.DataSourceProvider;
-import com.google.gerrit.server.schema.DatabaseModule;
+import com.google.gerrit.server.schema.SchemaVersion;
import com.google.gerrit.sshd.SshModule;
import com.google.gerrit.sshd.commands.MasterCommandModule;
+import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.AbstractModule;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
-import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
-import com.google.inject.name.Names;
+import com.google.inject.TypeLiteral;
import com.google.inject.servlet.GuiceServletContextListener;
import com.google.inject.spi.Message;
@@ -51,7 +55,6 @@
import javax.servlet.ServletContextEvent;
import javax.servlet.http.HttpServletRequest;
-import javax.sql.DataSource;
/** Configures the web application environment for Gerrit Code Review. */
public class WebAppInitializer extends GuiceServletContextListener {
@@ -129,7 +132,7 @@
bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath);
bind(DataSourceProvider.Context.class).toInstance(
DataSourceProvider.Context.MULTI_USER);
- bind(Key.get(DataSource.class, Names.named("ReviewDb"))).toProvider(
+ bind(new TypeLiteral<SchemaFactory<ReviewDb>>() {}).toProvider(
DataSourceProvider.class).in(SINGLETON);
listener().to(DataSourceProvider.class);
}
@@ -140,13 +143,13 @@
modules.add(new LifecycleModule() {
@Override
protected void configure() {
- bind(Key.get(DataSource.class, Names.named("ReviewDb"))).toProvider(
+ bind(new TypeLiteral<SchemaFactory<ReviewDb>>() {}).toProvider(
ReviewDbDataSourceProvider.class).in(SINGLETON);
listener().to(ReviewDbDataSourceProvider.class);
}
});
}
- modules.add(new DatabaseModule());
+ modules.add(new SchemaVersion.Module());
return Guice.createInjector(PRODUCTION, modules);
}
@@ -173,6 +176,9 @@
private Injector createSysInjector() {
final List<Module> modules = new ArrayList<Module>();
modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
+ modules.add(new SmtpEmailSender.Module());
+ modules.add(new EhcachePoolImpl.Module());
+ modules.add(new LocalDiskRepositoryManager.Module());
modules.add(new CanonicalWebUrlModule() {
@Override
protected Class<? extends Provider<String>> provider() {
@@ -193,6 +199,8 @@
private Injector createWebInjector() {
final List<Module> modules = new ArrayList<Module>();
modules.add(sshInjector.getInstance(WebModule.class));
+ modules.add(sshInjector.getInstance(WebSshGlueModule.class));
+ modules.add(new SessionCacheCleaner.Module());
return sysInjector.createChildInjector(modules);
}
diff --git a/pom.xml b/pom.xml
index 26a065a..93ee2be 100644
--- a/pom.xml
+++ b/pom.xml
@@ -47,7 +47,7 @@
<properties>
<jgitVersion>0.8.4.242-g09130b8</jgitVersion>
- <gwtormVersion>1.1.4</gwtormVersion>
+ <gwtormVersion>1.2-SNAPSHOT</gwtormVersion>
<gwtjsonrpcVersion>1.2.2</gwtjsonrpcVersion>
<gwtexpuiVersion>1.2.1</gwtexpuiVersion>
<gwtVersion>2.0.4</gwtVersion>
@@ -74,6 +74,7 @@
<module>gerrit-util-ssl</module>
<module>gerrit-common</module>
+ <module>gerrit-ehcache</module>
<module>gerrit-httpd</module>
<module>gerrit-launcher</module>
<module>gerrit-main</module>
@@ -531,6 +532,12 @@
</dependency>
<dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>r06</version>
+ </dependency>
+
+ <dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>2.0</version>
@@ -561,6 +568,12 @@
</dependency>
<dependency>
+ <groupId>com.google.protobuf</groupId>
+ <artifactId>protobuf-java</artifactId>
+ <version>2.3.0</version>
+ </dependency>
+
+ <dependency>
<groupId>eu.medsea.mimeutil</groupId>
<artifactId>mime-util</artifactId>
<version>2.1.3</version>