Rewrite our build as modular maven components

This refactoring splits the code up into different components, with
their own per-component CLASSPATH.  By moving all of our classes
into isolated components we can better isolate the classpaths and
try to avoid unexpected dependency problems.  It also allows us to
more clearly define which components are used by the GWT UI and
thus must be compiled under GWT, and which components are run on
the server and can therefore use more of the J2SE API.

Change-Id: I833cc22bacc5655d1c9099ed7c2b0e0a5b08855a
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2009-11-07 12:55:26 -08:00
parent 2464ac82b7
commit 44671f5c69
719 changed files with 9467 additions and 2417 deletions

4
gerrit-httpd/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/target
/.classpath
/.project
/.settings/org.maven.ide.eclipse.prefs

View File

@@ -0,0 +1,3 @@
#Tue Sep 02 16:59:24 PDT 2008
eclipse.preferences.version=1
encoding/<project>=UTF-8

View File

@@ -0,0 +1,3 @@
#Tue Sep 02 16:59:24 PDT 2008
eclipse.preferences.version=1
line.separator=\n

View File

@@ -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

View File

@@ -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

67
gerrit-httpd/pom.xml Normal file
View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<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.0.25-SNAPSHOT</version>
</parent>
<artifactId>gerrit-httpd</artifactId>
<name>Gerrit Code Review - HTTPd</name>
<description>
Servlet context for components run inside of an HTTP environment.
</description>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-servlet</artifactId>
</dependency>
<dependency>
<groupId>eu.medsea.mimeutil</groupId>
<artifactId>mime-util</artifactId>
</dependency>
<dependency>
<groupId>org.openid4java</groupId>
<artifactId>openid4java-consumer</artifactId>
</dependency>
<dependency>
<groupId>gwtjsonrpc</groupId>
<artifactId>gwtjsonrpc</artifactId>
</dependency>
<dependency>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-server</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,98 @@
// 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.
//
// This code is based heavily on Robert Harder's <rob@iharder.net>
// public domain Base64 class, version 2.1.
//
package com.google.gerrit.httpd;
/** Base64 encoder which uses a language safe within HTTP cookies. */
class CookieBase64 {
private static final char[] enc;
static {
enc = new char[64];
int o = 0;
o = fill(enc, o, 'a', 'z');
o = fill(enc, o, 'A', 'Z');
o = fill(enc, o, '0', '9');
enc[o++] = '-';
enc[o++] = '.';
}
private static int fill(final char[] out, int o, final char f, final int l) {
for (char c = f; c <= l; c++)
out[o++] = c;
return o;
}
static String encode(final byte[] in) {
final StringBuilder out = new StringBuilder(in.length * 4 / 3);
final int len2 = in.length - 2;
int d = 0;
for (; d < len2; d += 3) {
encode3to4(out, in, d, 3);
}
if (d < in.length) {
encode3to4(out, in, d, in.length - d);
}
return out.toString();
}
private static void encode3to4(final StringBuilder out, final byte[] in,
final int inOffset, final int numSigBytes) {
// 1 2 3
// 01234567890123456789012345678901 Bit position
// --------000000001111111122222222 Array position from threeBytes
// --------| || || || | Six bit groups to index ALPHABET
// >>18 >>12 >> 6 >> 0 Right shift necessary
// 0x3f 0x3f 0x3f Additional AND
// Create buffer with zero-padding if there are only one or two
// significant bytes passed in the array.
// We have to shift left 24 in order to flush out the 1's that appear
// when Java treats a value as negative that is cast from a byte to an int.
//
int inBuff = ( numSigBytes > 0 ? ((in[ inOffset ] << 24) >>> 8) : 0 )
| ( numSigBytes > 1 ? ((in[ inOffset + 1 ] << 24) >>> 16) : 0 )
| ( numSigBytes > 2 ? ((in[ inOffset + 2 ] << 24) >>> 24) : 0 );
switch (numSigBytes) {
case 3:
out.append(enc[(inBuff >>> 18)]);
out.append(enc[(inBuff >>> 12) & 0x3f]);
out.append(enc[(inBuff >>> 6) & 0x3f]);
out.append(enc[(inBuff) & 0x3f]);
break;
case 2:
out.append(enc[(inBuff >>> 18)]);
out.append(enc[(inBuff >>> 12) & 0x3f]);
out.append(enc[(inBuff >>> 6) & 0x3f]);
break;
case 1:
out.append(enc[(inBuff >>> 18)]);
out.append(enc[(inBuff >>> 12) & 0x3f]);
break;
default:
break;
}
}
private CookieBase64() {
}
}

View File

@@ -0,0 +1,126 @@
// 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 com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.GerritConfig;
import com.google.gerrit.common.data.GitwebLink;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.Nullable;
import com.google.gerrit.server.config.WildProjectName;
import com.google.gerrit.server.contact.ContactStore;
import com.google.gerrit.server.mail.EmailSender;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gwtexpui.safehtml.client.RegexFindReplace;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.jgit.lib.Config;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
class GerritConfigProvider implements Provider<GerritConfig> {
private final Realm realm;
private final Config cfg;
private final String canonicalWebUrl;
private final AuthConfig authConfig;
private final Project.NameKey wildProject;
private final SshInfo sshInfo;
private final ApprovalTypes approvalTypes;
private EmailSender emailSender;
private final ContactStore contactStore;
@Inject
GerritConfigProvider(final Realm r, @GerritServerConfig final Config gsc,
@CanonicalWebUrl @Nullable final String cwu, final AuthConfig ac,
@WildProjectName final Project.NameKey wp, final SshInfo si,
final ApprovalTypes at, final ContactStore cs) {
realm = r;
cfg = gsc;
canonicalWebUrl = cwu;
authConfig = ac;
sshInfo = si;
wildProject = wp;
approvalTypes = at;
contactStore = cs;
}
@Inject(optional = true)
void setEmailSender(final EmailSender d) {
emailSender = d;
}
private GerritConfig create() {
final GerritConfig config = new GerritConfig();
config.setCanonicalUrl(canonicalWebUrl);
config.setUseContributorAgreements(cfg.getBoolean("auth",
"contributoragreements", false));
config.setGitDaemonUrl(cfg.getString("gerrit", null, "canonicalgiturl"));
config.setUseRepoDownload(cfg.getBoolean("repo", null,
"showdownloadcommand", false));
config.setUseContactInfo(contactStore != null && contactStore.isEnabled());
config.setAuthType(authConfig.getAuthType());
config.setWildProject(wildProject);
config.setApprovalTypes(approvalTypes);
final Set<Account.FieldName> fields = new HashSet<Account.FieldName>();
for (final Account.FieldName n : Account.FieldName.values()) {
if (realm.allowsEdit(n)) {
fields.add(n);
}
}
if (emailSender != null && emailSender.isEnabled()) {
fields.add(Account.FieldName.REGISTER_NEW_EMAIL);
}
config.setEditableAccountFields(fields);
final String gitwebUrl = cfg.getString("gitweb", null, "url");
if (gitwebUrl != null) {
config.setGitwebLink(new GitwebLink(gitwebUrl));
}
if (sshInfo != null && !sshInfo.getHostKeys().isEmpty()) {
config.setSshdAddress(sshInfo.getHostKeys().get(0).getHost());
}
ArrayList<String> commentLinkNames =
new ArrayList<String>(cfg.getSubsections("CommentLink"));
ArrayList<RegexFindReplace> commentLinks =
new ArrayList<RegexFindReplace>(commentLinkNames.size());
for (String commentLinkName : commentLinkNames) {
String match = cfg.getString("commentlink", commentLinkName, "match");
String link =
"<a href=\"" + cfg.getString("commentlink", commentLinkName, "link")
+ "\">$&</a>";
commentLinks.add(new RegexFindReplace(match, link));
}
config.setCommentLinks(commentLinks);
return config;
}
@Override
public GerritConfig get() {
return create();
}
}

View File

@@ -0,0 +1,245 @@
// 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.httpd;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.zip.GZIPOutputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
/** Utility functions to deal with HTML using W3C DOM operations. */
public class HtmlDomUtil {
/** Standard character encoding we prefer (UTF-8). */
public static final String ENC = "UTF-8";
/** DOCTYPE for a standards mode HTML document. */
public static final String HTML_STRICT =
"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd";
/** Convert a document to a UTF-8 byte sequence. */
public static byte[] toUTF8(final Document hostDoc) throws IOException {
return toString(hostDoc).getBytes(ENC);
}
/** Compress the document. */
public static byte[] compress(final byte[] raw) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final GZIPOutputStream gz = new GZIPOutputStream(out);
gz.write(raw);
gz.finish();
gz.flush();
return out.toByteArray();
}
/** Convert a document to a String, assuming later encoding to UTF-8. */
public static String toString(final Document hostDoc) throws IOException {
try {
final StringWriter out = new StringWriter();
final DOMSource domSource = new DOMSource(hostDoc);
final StreamResult streamResult = new StreamResult(out);
final TransformerFactory tf = TransformerFactory.newInstance();
final Transformer serializer = tf.newTransformer();
serializer.setOutputProperty(OutputKeys.ENCODING, ENC);
serializer.setOutputProperty(OutputKeys.METHOD, "html");
serializer.setOutputProperty(OutputKeys.INDENT, "no");
serializer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,
HtmlDomUtil.HTML_STRICT);
serializer.transform(domSource, streamResult);
return out.toString();
} catch (TransformerConfigurationException e) {
final IOException r = new IOException("Error transforming page");
r.initCause(e);
throw r;
} catch (TransformerException e) {
final IOException r = new IOException("Error transforming page");
r.initCause(e);
throw r;
}
}
/** Find an element by its "id" attribute; null if no element is found. */
public static Element find(final Node parent, final String name) {
final NodeList list = parent.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
final Node n = list.item(i);
if (n instanceof Element) {
final Element e = (Element) n;
if (name.equals(e.getAttribute("id"))) {
return e;
}
}
final Element r = find(n, name);
if (r != null) {
return r;
}
}
return null;
}
/** Append an HTML &lt;input type="hidden"&gt; to the form. */
public static void addHidden(final Element form, final String name,
final String value) {
final Element in = form.getOwnerDocument().createElement("input");
in.setAttribute("type", "hidden");
in.setAttribute("name", name);
in.setAttribute("value", value);
form.appendChild(in);
}
/** Clone a document so it can be safely modified on a per-request basis. */
public static Document clone(final Document doc) throws IOException {
final Document d;
try {
d = newBuilder().newDocument();
} catch (ParserConfigurationException e) {
throw new IOException("Cannot clone document");
}
final Node n = d.importNode(doc.getDocumentElement(), true);
d.appendChild(n);
return d;
}
/** Parse an XHTML file from our CLASSPATH and return the instance. */
public static Document parseFile(final Class<?> context, final String name)
throws IOException {
final InputStream in;
in = context.getResourceAsStream(name);
if (in == null) {
return null;
}
try {
try {
try {
return newBuilder().parse(in);
} catch (SAXException e) {
throw new IOException("Error reading " + name, e);
} catch (ParserConfigurationException e) {
throw new IOException("Error reading " + name, e);
}
} finally {
in.close();
}
} catch (IOException e) {
throw new IOException("Error reading " + name, e);
}
}
/** Read a Read a UTF-8 text file from our CLASSPATH and return it. */
public static String readFile(final Class<?> context, final String name)
throws IOException {
final InputStream in = context.getResourceAsStream(name);
if (in == null) {
return null;
}
try {
return asString(in);
} catch (IOException e) {
throw new IOException("Error reading " + name, e);
}
}
/** Parse an XHTML file from the local drive and return the instance. */
public static Document parseFile(final File parentDir, final String name)
throws IOException {
if (parentDir == null) {
return null;
}
final File path = new File(parentDir, name);
try {
final InputStream in = new FileInputStream(path);
try {
try {
return newBuilder().parse(in);
} catch (SAXException e) {
throw new IOException("Error reading " + path, e);
} catch (ParserConfigurationException e) {
throw new IOException("Error reading " + path, e);
}
} finally {
in.close();
}
} catch (FileNotFoundException e) {
return null;
} catch (IOException e) {
throw new IOException("Error reading " + path, e);
}
}
/** Read a UTF-8 text file from the local drive. */
public static String readFile(final File parentDir, final String name)
throws IOException {
if (parentDir == null) {
return null;
}
final File path = new File(parentDir, name);
try {
return asString(new FileInputStream(path));
} catch (FileNotFoundException e) {
return null;
} catch (IOException e) {
throw new IOException("Error reading " + path, e);
}
}
private static String asString(final InputStream in)
throws UnsupportedEncodingException, IOException {
try {
final StringBuilder w = new StringBuilder();
final InputStreamReader r = new InputStreamReader(in, ENC);
final char[] buf = new char[512];
int n;
while ((n = r.read(buf)) > 0) {
w.append(buf, 0, n);
}
return w.toString();
} finally {
in.close();
}
}
private static DocumentBuilder newBuilder()
throws ParserConfigurationException {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
factory.setExpandEntityReferences(false);
factory.setIgnoringComments(true);
final DocumentBuilder parser = factory.newDocumentBuilder();
return parser;
}
}

View File

@@ -0,0 +1,81 @@
// 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 com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.CanonicalWebUrlProvider;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.Inject;
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import org.eclipse.jgit.lib.Config;
import javax.servlet.http.HttpServletRequest;
/** Sets {@link CanonicalWebUrl} to current HTTP request if not configured. */
class HttpCanonicalWebUrlProvider extends CanonicalWebUrlProvider {
private Provider<HttpServletRequest> requestProvider;
@Inject
HttpCanonicalWebUrlProvider(@GerritServerConfig final Config config) {
super(config);
}
@Inject(optional = true)
void setHttpServletRequest(final Provider<HttpServletRequest> hsr) {
requestProvider = hsr;
}
@Override
public String get() {
String canonicalUrl = super.get();
if (canonicalUrl != null) {
return canonicalUrl;
}
if (requestProvider != null) {
// No canonical URL configured? Maybe we can get a reasonable
// guess from the incoming HTTP request, if we are currently
// inside of an HTTP request scope.
//
final HttpServletRequest req;
try {
req = requestProvider.get();
} catch (ProvisionException noWeb) {
if (noWeb.getCause() instanceof OutOfScopeException) {
// We can't obtain the request as we are not inside of
// an HTTP request scope. Callers must handle null.
//
return null;
} else {
throw noWeb;
}
}
final StringBuffer url = req.getRequestURL();
url.setLength(url.length() - req.getServletPath().length());
if (url.charAt(url.length() - 1) != '/') {
url.append('/');
}
return url.toString();
}
// We have no way of guessing our HTTP url.
//
return null;
}
}

View File

@@ -0,0 +1,35 @@
// 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 com.google.gerrit.server.CurrentUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.servlet.RequestScoped;
@RequestScoped
class HttpCurrentUserProvider implements Provider<CurrentUser> {
private final WebSession session;
@Inject
HttpCurrentUserProvider(final WebSession session) {
this.session = session;
}
@Override
public CurrentUser get() {
return session.getCurrentUser();
}
}

View File

@@ -0,0 +1,42 @@
// 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 com.google.gerrit.common.errors.NotSignedInException;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.servlet.RequestScoped;
@RequestScoped
class HttpIdentifiedUserProvider implements Provider<IdentifiedUser> {
private final CurrentUser user;
@Inject
HttpIdentifiedUserProvider(final CurrentUser u) {
user = u;
}
@Override
public IdentifiedUser get() {
if (user instanceof IdentifiedUser) {
return (IdentifiedUser) user;
}
throw new ProvisionException(NotSignedInException.MESSAGE,
new NotSignedInException());
}
}

View File

@@ -0,0 +1,57 @@
// 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 com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.Nullable;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Singleton
class HttpLogoutServlet extends HttpServlet {
private final Provider<WebSession> webSession;
private final Provider<String> urlProvider;
private final String logoutUrl;
@Inject
HttpLogoutServlet(final AuthConfig authConfig,
final Provider<WebSession> webSession,
@CanonicalWebUrl @Nullable final Provider<String> urlProvider,
final AccountManager accountManager) {
this.webSession = webSession;
this.urlProvider = urlProvider;
this.logoutUrl = authConfig.getLogoutURL();
}
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
webSession.get().logout();
if (logoutUrl != null) {
rsp.sendRedirect(logoutUrl);
} else {
rsp.sendRedirect(urlProvider.get());
}
}
}

View File

@@ -0,0 +1,48 @@
// 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 com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.servlet.RequestScoped;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import javax.servlet.http.HttpServletRequest;
@RequestScoped
class HttpRemotePeerProvider implements Provider<SocketAddress> {
private final HttpServletRequest req;
@Inject
HttpRemotePeerProvider(final HttpServletRequest r) {
req = r;
}
@Override
public SocketAddress get() {
final String addr = req.getRemoteAddr();
final int port = req.getRemotePort();
try {
return new InetSocketAddress(InetAddress.getByName(addr), port);
} catch (UnknownHostException e) {
throw new ProvisionException("Cannot get @RemotePeer", e);
}
}
}

View File

@@ -0,0 +1,59 @@
// 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 com.google.gerrit.server.RequestCleanup;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/** Executes any pending {@link RequestCleanup} at the end of a request. */
@Singleton
class RequestCleanupFilter implements Filter {
private final Provider<RequestCleanup> cleanup;
@Inject
RequestCleanupFilter(final Provider<RequestCleanup> r) {
cleanup = r;
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
@Override
public void doFilter(final ServletRequest request,
final ServletResponse response, final FilterChain chain)
throws IOException, ServletException {
try {
chain.doFilter(request, response);
} finally {
cleanup.get().run();
}
}
}

View File

@@ -0,0 +1,132 @@
// 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.inject.Scopes.SINGLETON;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.httpd.raw.CatServlet;
import com.google.gerrit.httpd.raw.HostPageServlet;
import com.google.gerrit.httpd.raw.LegacyGerritServlet;
import com.google.gerrit.httpd.raw.PrettifyServlet;
import com.google.gerrit.httpd.raw.SshInfoServlet;
import com.google.gerrit.httpd.raw.StaticServlet;
import com.google.gerrit.reviewdb.RevId;
import com.google.gwtexpui.server.CacheControlFilter;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.servlet.ServletModule;
import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
class UrlModule extends ServletModule {
@Override
protected void configureServlets() {
filter("/*").through(Key.get(CacheControlFilter.class));
bind(Key.get(CacheControlFilter.class)).in(SINGLETON);
serve("/").with(HostPageServlet.class);
serve("/Gerrit").with(LegacyGerritServlet.class);
serve("/Gerrit/*").with(legacyGerritScreen());
serve("/cat/*").with(CatServlet.class);
serve("/logout").with(HttpLogoutServlet.class);
serve("/prettify/*").with(PrettifyServlet.class);
serve("/signout").with(HttpLogoutServlet.class);
serve("/ssh_info").with(SshInfoServlet.class);
serve("/static/*").with(StaticServlet.class);
serve("/Main.class").with(notFound());
serve("/com/google/gerrit/main/*").with(notFound());
serve("/all").with(screen(PageLinks.ALL_MERGED));
serve("/mine").with(screen(PageLinks.MINE));
serve("/open").with(screen(PageLinks.ALL_OPEN));
serve("/settings").with(screen(PageLinks.SETTINGS));
serve("/starred").with(screen(PageLinks.MINE_STARRED));
serveRegex( //
"^/([1-9][0-9]*)/?$", //
"^/r/([0-9a-fA-F]{4," + RevId.LEN + "})/?$" //
).with(changeQuery());
}
private Key<HttpServlet> notFound() {
return key(new HttpServlet() {
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
}
});
}
private Key<HttpServlet> screen(final String target) {
return key(new HttpServlet() {
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
toGerrit(target, req, rsp);
}
});
}
private Key<HttpServlet> legacyGerritScreen() {
return key(new HttpServlet() {
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
final String token = req.getPathInfo().substring(1);
toGerrit(token, req, rsp);
}
});
}
private Key<HttpServlet> changeQuery() {
return key(new HttpServlet() {
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
toGerrit(PageLinks.toChangeQuery(req.getPathInfo()), req, rsp);
}
});
}
private Key<HttpServlet> key(final HttpServlet servlet) {
final Key<HttpServlet> srv =
Key.get(HttpServlet.class, UniqueAnnotations.create());
bind(srv).toProvider(new Provider<HttpServlet>() {
@Override
public HttpServlet get() {
return servlet;
}
}).in(SINGLETON);
return srv;
}
private void toGerrit(final String target, final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
final StringBuilder url = new StringBuilder();
url.append(req.getContextPath());
url.append('/');
url.append('#');
url.append(target);
rsp.sendRedirect(url.toString());
}
}

View File

@@ -0,0 +1,122 @@
// 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.inject.Scopes.SINGLETON;
import com.google.gerrit.common.data.GerritConfig;
import com.google.gerrit.httpd.auth.become.BecomeAnyAccountLoginServlet;
import com.google.gerrit.httpd.auth.container.HttpAuthModule;
import com.google.gerrit.httpd.auth.ldap.LdapAuthModule;
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
import com.google.gerrit.httpd.rpc.UiRpcModule;
import com.google.gerrit.reviewdb.AuthType;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.FactoryModule;
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.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.servlet.RequestScoped;
import com.google.inject.servlet.ServletModule;
import java.net.SocketAddress;
class WebModule extends FactoryModule {
private final Provider<SshInfo> sshInfoProvider;
private final Provider<SshKeyCache> sshKeyCacheProvider;
private final AuthType loginType;
@Inject
WebModule(final Provider<SshInfo> sshInfoProvider,
final Provider<SshKeyCache> sshKeyCacheProvider,
final AuthConfig authConfig) {
this(sshInfoProvider, sshKeyCacheProvider, authConfig.getAuthType());
}
WebModule(final Provider<SshInfo> sshInfoProvider,
final Provider<SshKeyCache> sshKeyCacheProvider, final AuthType loginType) {
this.sshInfoProvider = sshInfoProvider;
this.sshKeyCacheProvider = sshKeyCacheProvider;
this.loginType = loginType;
}
@Override
protected void configure() {
install(new ServletModule() {
@Override
protected void configureServlets() {
filter("/*").through(RequestCleanupFilter.class);
}
});
switch (loginType) {
case OPENID:
install(new OpenIdModule());
break;
case HTTP:
case HTTP_LDAP:
install(new HttpAuthModule());
break;
case LDAP:
install(new LdapAuthModule());
break;
case DEVELOPMENT_BECOME_ANY_ACCOUNT:
install(new ServletModule() {
@Override
protected void configureServlets() {
serve("/become").with(BecomeAnyAccountLoginServlet.class);
}
});
break;
default:
throw new ProvisionException("Unsupported loginType: " + loginType);
}
install(new UrlModule());
install(new UiRpcModule());
install(new GerritRequestModule());
bind(SshInfo.class).toProvider(sshInfoProvider);
bind(SshKeyCache.class).toProvider(sshKeyCacheProvider);
bind(ContactStore.class).toProvider(ContactStoreProvider.class).in(
SINGLETON);
bind(GerritConfig.class).toProvider(GerritConfigProvider.class).in(
SINGLETON);
bind(AccountManager.class).in(SINGLETON);
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
HttpRemotePeerProvider.class).in(RequestScoped.class);
install(WebSession.module());
bind(CurrentUser.class).toProvider(HttpCurrentUserProvider.class).in(
RequestScoped.class);
bind(IdentifiedUser.class).toProvider(HttpIdentifiedUserProvider.class).in(
RequestScoped.class);
}
}

View File

@@ -0,0 +1,177 @@
// 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 java.util.concurrent.TimeUnit.HOURS;
import com.google.gerrit.httpd.WebSessionManager.Key;
import com.google.gerrit.httpd.WebSessionManager.Val;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.gerrit.server.cache.EvictionPolicy;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;
import com.google.inject.servlet.RequestScoped;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RequestScoped
public final class WebSession {
private static final String ACCOUNT_COOKIE = "GerritAccount";
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) //
.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);
}
};
}
private final HttpServletRequest request;
private final HttpServletResponse response;
private final WebSessionManager manager;
private final AnonymousUser anonymous;
private final IdentifiedUser.RequestFactory identified;
private Cookie outCookie;
private Key key;
private Val val;
@Inject
WebSession(final HttpServletRequest request,
final HttpServletResponse response, final WebSessionManager manager,
final AnonymousUser anonymous,
final IdentifiedUser.RequestFactory identified) {
this.request = request;
this.response = response;
this.manager = manager;
this.anonymous = anonymous;
this.identified = identified;
final String cookie = readCookie();
if (cookie != null) {
key = new Key(cookie);
val = manager.get(key);
} else {
key = null;
val = null;
}
if (isSignedIn() && val.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);
saveCookie();
}
}
private String readCookie() {
final Cookie[] all = request.getCookies();
if (all != null) {
for (final Cookie c : all) {
if (ACCOUNT_COOKIE.equals(c.getName())) {
final String v = c.getValue();
return v != null && !"".equals(v) ? v : null;
}
}
}
return null;
}
public boolean isSignedIn() {
return val != null;
}
String getToken() {
return isSignedIn() ? key.getToken() : null;
}
public boolean isTokenValid(final String inputToken) {
return isSignedIn() && key.getToken().equals(inputToken);
}
CurrentUser getCurrentUser() {
if (isSignedIn()) {
return identified.create(AccessPath.WEB, val.getAccountId());
}
return anonymous;
}
public void login(final Account.Id id, final boolean rememberMe) {
logout();
key = manager.createKey(id);
val = manager.createVal(key, id, rememberMe);
saveCookie();
}
public void logout() {
if (val != null) {
manager.destroy(key);
key = null;
val = null;
saveCookie();
}
}
private void saveCookie() {
final String token;
final int ageSeconds;
if (key == null) {
token = "";
ageSeconds = 0 /* erase at client */;
} else {
token = key.getToken();
ageSeconds = manager.getCookieAge(val);
}
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);
}
}
}

View File

@@ -0,0 +1,212 @@
// 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.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) {
return createVal(key, val.getAccountId(), val.isPersistentCookie());
}
Val createVal(final Key key, final Account.Id who, final boolean remember) {
// 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);
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;
Val(final Account.Id accountId, final long refreshCookieAt,
final boolean persistentCookie) {
this.accountId = accountId;
this.refreshCookieAt = refreshCookieAt;
this.persistentCookie = persistentCookie;
}
Account.Id getAccountId() {
return accountId;
}
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);
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;
default:
throw new IOException("Unknown tag found in object: " + tag);
}
}
}
}
}

View File

@@ -0,0 +1,25 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html>
<head>
<title>Gerrit Code Review</title>
</head>
<body>
<form method="GET">
<b>ssh_user_name:</b>
<input type="text" size="30" name="ssh_user_name" />
<input type="submit" value="Become Account" />
</form>
<form method="GET">
<b>preferred_email:</b>
<input type="text" size="30" name="preferred_email" />
<input type="submit" value="Become Account" />
</form>
<form method="GET">
<b>account_id:</b>
<input type="text" size="12" name="account_id" />
<input type="submit" value="Become Account" />
</form>
</body>
</html>

View File

@@ -0,0 +1,175 @@
// 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.auth.become;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.Nullable;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Collections;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@SuppressWarnings("serial")
@Singleton
public class BecomeAnyAccountLoginServlet extends HttpServlet {
private final SchemaFactory<ReviewDb> schema;
private final Provider<WebSession> webSession;
private final Provider<String> urlProvider;
private final byte[] raw;
@Inject
BecomeAnyAccountLoginServlet(final Provider<WebSession> ws,
final SchemaFactory<ReviewDb> sf,
final @CanonicalWebUrl @Nullable Provider<String> up,
final ServletContext servletContext) throws IOException {
webSession = ws;
schema = sf;
urlProvider = up;
final String pageName = "BecomeAnyAccount.html";
final String doc = HtmlDomUtil.readFile(getClass(), pageName);
if (doc == null) {
throw new FileNotFoundException("No " + pageName + " in webapp");
}
raw = doc.getBytes(HtmlDomUtil.ENC);
}
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
doPost(req, rsp);
}
@Override
protected void doPost(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
rsp.setHeader("Pragma", "no-cache");
rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
final List<Account> accounts;
if (req.getParameter("ssh_user_name") != null) {
accounts = bySshUserName(rsp, req.getParameter("ssh_user_name"));
} else if (req.getParameter("preferred_email") != null) {
accounts = byPreferredEmail(rsp, req.getParameter("preferred_email"));
} else if (req.getParameter("account_id") != null) {
accounts = byAccountId(rsp, req.getParameter("account_id"));
} else {
rsp.setContentType("text/html");
rsp.setCharacterEncoding(HtmlDomUtil.ENC);
rsp.setContentLength(raw.length);
final OutputStream out = rsp.getOutputStream();
try {
out.write(raw);
} finally {
out.close();
}
return;
}
if (accounts.size() == 1) {
final Account account = accounts.get(0);
webSession.get().login(account.getId(), false);
rsp.sendRedirect(urlProvider.get());
} else {
rsp.setContentType("text/html");
rsp.setCharacterEncoding(HtmlDomUtil.ENC);
final Writer out = rsp.getWriter();
out.write("<html>");
out.write("<body>");
out.write("<h1>Account Not Found</h1>");
out.write("</body>");
out.write("</html>");
out.close();
}
}
private List<Account> bySshUserName(final HttpServletResponse rsp,
final String userName) {
try {
final ReviewDb db = schema.open();
try {
final Account account = db.accounts().bySshUserName(userName);
return account != null ? Collections.<Account> singletonList(account)
: Collections.<Account> emptyList();
} finally {
db.close();
}
} catch (OrmException e) {
getServletContext().log("cannot query database", e);
return Collections.<Account> emptyList();
}
}
private List<Account> byPreferredEmail(final HttpServletResponse rsp,
final String email) {
try {
final ReviewDb db = schema.open();
try {
return db.accounts().byPreferredEmail(email).toList();
} finally {
db.close();
}
} catch (OrmException e) {
getServletContext().log("cannot query database", e);
return Collections.<Account> emptyList();
}
}
private List<Account> byAccountId(final HttpServletResponse rsp,
final String idStr) {
final Account.Id id;
try {
id = Account.Id.parse(idStr);
} catch (NumberFormatException nfe) {
return Collections.<Account> emptyList();
}
try {
final ReviewDb db = schema.open();
try {
final Account account = db.accounts().get(id);
return account != null ? Collections.<Account> singletonList(account)
: Collections.<Account> emptyList();
} finally {
db.close();
}
} catch (OrmException e) {
getServletContext().log("cannot query database", e);
return Collections.<Account> emptyList();
}
}
}

View File

@@ -0,0 +1,116 @@
// 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.auth.container;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.httpd.raw.HostPageServlet;
import com.google.gwt.user.server.rpc.RPCServletUtils;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Watches request for the host page and requires login if not yet signed in.
* <p>
* If HTTP authentication has been enabled on this server this filter is bound
* in front of the {@link HostPageServlet} and redirects users who are not yet
* signed in to visit {@code /login/}, so the web container can force login.
* This redirect is performed with JavaScript, such that any existing anchor
* token in the URL can be rewritten and preserved through the authentication
* process of any enterprise single sign-on solutions.
*/
@Singleton
class HttpAuthFilter implements Filter {
private final Provider<WebSession> webSession;
private final byte[] signInRaw;
private final byte[] signInGzip;
@Inject
HttpAuthFilter(final Provider<WebSession> webSession,
final ServletContext servletContext) throws IOException {
this.webSession = webSession;
final String pageName = "LoginRedirect.html";
final String doc = HtmlDomUtil.readFile(getClass(), pageName);
if (doc == null) {
throw new FileNotFoundException("No " + pageName + " in webapp");
}
signInRaw = doc.getBytes(HtmlDomUtil.ENC);
signInGzip = HtmlDomUtil.compress(signInRaw);
}
@Override
public void doFilter(final ServletRequest request,
final ServletResponse response, final FilterChain chain)
throws IOException, ServletException {
if (!webSession.get().isSignedIn()) {
// Not signed in yet. Since the browser state might have an anchor
// token which we want to capture and carry through the auth process
// we send back JavaScript now to capture that, and do the real work
// of redirecting to the authentication area.
//
final HttpServletRequest req = (HttpServletRequest) request;
final HttpServletResponse rsp = (HttpServletResponse) response;
final byte[] tosend;
if (RPCServletUtils.acceptsGzipEncoding(req)) {
rsp.setHeader("Content-Encoding", "gzip");
tosend = signInGzip;
} else {
tosend = signInRaw;
}
rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
rsp.setHeader("Pragma", "no-cache");
rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
rsp.setContentType("text/html");
rsp.setCharacterEncoding(HtmlDomUtil.ENC);
rsp.setContentLength(tosend.length);
final OutputStream out = rsp.getOutputStream();
try {
out.write(tosend);
} finally {
out.close();
}
} else {
// Already signed in, forward the request.
//
chain.doFilter(request, response);
}
}
@Override
public void init(final FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}

View File

@@ -0,0 +1,26 @@
// 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.auth.container;
import com.google.inject.servlet.ServletModule;
/** Servlets and support related to HTTP authentication. */
public class HttpAuthModule extends ServletModule {
@Override
protected void configureServlets() {
filter("/").through(HttpAuthFilter.class);
serve("/login/*").with(HttpLoginServlet.class);
}
}

View File

@@ -0,0 +1,171 @@
// 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.auth.container;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.httpd.WebSession;
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.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.Nullable;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Initializes the user session if HTTP authentication is enabled.
* <p>
* If HTTP authentication has been enabled this servlet binds to {@code /login/}
* and initializes the user session based on user information contained in the
* HTTP request.
*/
@Singleton
class HttpLoginServlet extends HttpServlet {
private static final Logger log =
LoggerFactory.getLogger(HttpLoginServlet.class);
private static final String AUTHORIZATION = "Authorization";
private final Provider<WebSession> webSession;
private final Provider<String> urlProvider;
private final AccountManager accountManager;
private final String loginHeader;
@Inject
HttpLoginServlet(final AuthConfig authConfig,
final Provider<WebSession> webSession,
@CanonicalWebUrl @Nullable final Provider<String> urlProvider,
final AccountManager accountManager) {
this.webSession = webSession;
this.urlProvider = urlProvider;
this.accountManager = accountManager;
final String hdr = authConfig.getLoginHttpHeader();
this.loginHeader = hdr != null && !hdr.equals("") ? hdr : AUTHORIZATION;
}
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws ServletException, IOException {
final String token = getToken(req);
if ("logout".equals(token) || "signout".equals(token)) {
req.getRequestDispatcher("/logout").forward(req, rsp);
return;
}
final String user = getRemoteUser(req);
if (user == null || "".equals(user)) {
log.error("Unable to authenticate user by " + loginHeader
+ " request header. Check container or server configuration.");
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
final AuthRequest areq = AuthRequest.forUser(user);
final AuthResult arsp;
try {
arsp = accountManager.authenticate(areq);
} catch (AccountException e) {
log.error("Unable to authenticate user \"" + user + "\"", e);
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
final StringBuilder rdr = new StringBuilder();
rdr.append(urlProvider.get());
rdr.append('#');
if (arsp.isNew() && !token.startsWith(PageLinks.REGISTER + ",")) {
rdr.append(PageLinks.REGISTER);
rdr.append(',');
}
rdr.append(token);
webSession.get().login(arsp.getAccountId(), false);
rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
rsp.setHeader("Pragma", "no-cache");
rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
rsp.sendRedirect(rdr.toString());
}
private String getToken(final HttpServletRequest req) {
String token = req.getPathInfo();
if (token != null && token.startsWith("/")) {
token = token.substring(1);
}
if (token == null || token.isEmpty()) {
token = PageLinks.MINE;
}
return token;
}
private String getRemoteUser(final HttpServletRequest req) {
if (AUTHORIZATION.equals(loginHeader)) {
final String user = req.getRemoteUser();
if (user != null && !"".equals(user)) {
// The container performed the authentication, and has the user
// identity already decoded for us. Honor that as we have been
// configured to honor HTTP authentication.
//
return user;
}
// If the container didn't do the authentication we might
// have done it in the front-end web server. Try to split
// the identity out of the Authorization header and honor it.
//
String auth = req.getHeader(AUTHORIZATION);
if (auth == null || "".equals(auth)) {
return null;
} else if (auth.startsWith("Basic ")) {
auth = auth.substring("Basic ".length());
auth = new String(Base64.decode(auth));
final int c = auth.indexOf(':');
return c > 0 ? auth.substring(0, c) : null;
} else if (auth.startsWith("Digest ")) {
final int u = auth.indexOf("username=\"");
if (u <= 0) {
return null;
}
auth = auth.substring(u + 10);
final int e = auth.indexOf('"');
return e > 0 ? auth.substring(0, auth.indexOf('"')) : null;
} else {
return null;
}
} else {
// Nonstandard HTTP header. We have been told to trust this
// header blindly as-is.
//
final String user = req.getHeader(loginHeader);
return user != null && !"".equals(user) ? user : null;
}
}
}

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html>
<head>
<title>Gerrit Code Review</title>
<script type="text/javascript" language="javascript">
var href = window.location.href;
var p = href.indexOf('#');
var token;
if (p >= 0) {
token = href.substring(p + 1);
href = href.substring(0, p);
} else {
token = 'mine';
}
window.location.replace(href + 'login/' + token);
</script>
</head>
<body>
<p>Redirecting to <a href="login/mine">Gerrit Code Review</a>.</p>
</body>
</html>

View File

@@ -0,0 +1,33 @@
// 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.auth.ldap;
import com.google.gerrit.httpd.rpc.RpcServletModule;
import com.google.gerrit.httpd.rpc.UiRpcModule;
import com.google.inject.servlet.ServletModule;
/** RPC support related to username/password LDAP authentication. */
public class LdapAuthModule extends ServletModule {
@Override
protected void configureServlets() {
serve("/login/*").with(LoginRedirectServlet.class);
install(new RpcServletModule(UiRpcModule.PREFIX) {
@Override
protected void configureServlets() {
rpc(UserPassAuthServiceImpl.class);
}
});
}
}

View File

@@ -0,0 +1,76 @@
// 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.auth.ldap;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.auth.SignInMode;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.Nullable;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Singleton
class LoginRedirectServlet extends HttpServlet {
private final Provider<WebSession> webSession;
private final Provider<String> urlProvider;
@Inject
LoginRedirectServlet(final Provider<WebSession> webSession,
@CanonicalWebUrl @Nullable final Provider<String> urlProvider) {
this.webSession = webSession;
this.urlProvider = urlProvider;
}
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
final String token;
if (webSession.get().isSignedIn()) {
token = getToken(req);
} else {
final String msg = "Session cookie not available.";
token = "SignInFailure," + SignInMode.SIGN_IN + "," + msg;
}
final StringBuilder rdr = new StringBuilder();
rdr.append(urlProvider.get());
rdr.append('#');
rdr.append(token);
rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
rsp.setHeader("Pragma", "no-cache");
rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
rsp.sendRedirect(rdr.toString());
}
private String getToken(final HttpServletRequest req) {
String token = req.getPathInfo();
if (token != null && token.startsWith("/")) {
token = token.substring(1);
}
if (token == null || token.isEmpty()) {
token = PageLinks.MINE;
}
return token;
}
}

View File

@@ -0,0 +1,67 @@
// 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.auth.ldap;
import com.google.gerrit.common.auth.userpass.LoginResult;
import com.google.gerrit.common.auth.userpass.UserPassAuthService;
import com.google.gerrit.httpd.WebSession;
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.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import com.google.inject.Provider;
class UserPassAuthServiceImpl implements UserPassAuthService {
private final Provider<WebSession> webSession;
private final AccountManager accountManager;
@Inject
UserPassAuthServiceImpl(final Provider<WebSession> webSession,
final AccountManager accountManager) {
this.webSession = webSession;
this.accountManager = accountManager;
}
@Override
public void authenticate(final String username, final String password,
final AsyncCallback<LoginResult> callback) {
LoginResult result = new LoginResult();
if (username == null || "".equals(username) //
|| password == null || "".equals(password)) {
result.success = false;
callback.onSuccess(result);
return;
}
final AuthRequest req = AuthRequest.forUser(username);
req.setPassword(password);
final AuthResult res;
try {
res = accountManager.authenticate(req);
} catch (AccountException e) {
result.success = false;
callback.onSuccess(result);
return;
}
result.success = true;
result.isNew = res.isNew();
webSession.get().login(res.getAccountId(), false);
callback.onSuccess(result);
}
}

View File

@@ -0,0 +1,57 @@
// 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.httpd.auth.openid;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** Handles the <code>/OpenID</code> URL for web based single-sign-on. */
@SuppressWarnings("serial")
@Singleton
class OpenIdLoginServlet extends HttpServlet {
private final OpenIdServiceImpl impl;
@Inject
OpenIdLoginServlet(final OpenIdServiceImpl i) {
impl = i;
}
@Override
public void doGet(final HttpServletRequest req, final HttpServletResponse rsp)
throws IOException {
doPost(req, rsp);
}
@Override
public void doPost(final HttpServletRequest req, final HttpServletResponse rsp)
throws IOException {
try {
rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
rsp.setHeader("Pragma", "no-cache");
rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
impl.doAuth(req, rsp);
} catch (Exception e) {
getServletContext().log("Unexpected error during authentication", e);
rsp.reset();
rsp.sendError(500);
}
}
}

View File

@@ -0,0 +1,53 @@
// 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.auth.openid;
import static java.util.concurrent.TimeUnit.MINUTES;
import com.google.gerrit.httpd.rpc.RpcServletModule;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
import com.google.inject.TypeLiteral;
import com.google.inject.servlet.ServletModule;
import java.util.List;
/** Servlets and RPC support related to OpenID authentication. */
public class OpenIdModule extends ServletModule {
@Override
protected void configureServlets() {
install(new CacheModule() {
@SuppressWarnings("unchecked")
@Override
protected void configure() {
final TypeLiteral<Cache<String, List>> type =
new TypeLiteral<Cache<String, List>>() {};
core(type, "openid") //
.maxAge(5, MINUTES) // don't cache too long, might be stale
.memoryLimit(64) // short TTL means we won't have many entries
;
}
});
serve("/" + OpenIdServiceImpl.RETURN_URL).with(OpenIdLoginServlet.class);
install(new RpcServletModule(RpcServletModule.PREFIX) {
@Override
protected void configureServlets() {
rpc(OpenIdServiceImpl.class);
}
});
}
}

View File

@@ -0,0 +1,467 @@
// 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.auth.openid;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.auth.SignInMode;
import com.google.gerrit.common.auth.openid.DiscoveryResult;
import com.google.gerrit.common.auth.openid.OpenIdService;
import com.google.gerrit.common.auth.openid.OpenIdUrls;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.UrlEncoded;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.SelfPopulatingCache;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.Nullable;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtorm.client.KeyUtil;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import org.openid4java.consumer.ConsumerException;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.consumer.VerificationResult;
import org.openid4java.discovery.DiscoveryException;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.Message;
import org.openid4java.message.MessageException;
import org.openid4java.message.MessageExtension;
import org.openid4java.message.ParameterList;
import org.openid4java.message.ax.AxMessage;
import org.openid4java.message.ax.FetchRequest;
import org.openid4java.message.ax.FetchResponse;
import org.openid4java.message.sreg.SRegMessage;
import org.openid4java.message.sreg.SRegRequest;
import org.openid4java.message.sreg.SRegResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Singleton
class OpenIdServiceImpl implements OpenIdService {
private static final Logger log =
LoggerFactory.getLogger(OpenIdServiceImpl.class);
static final String RETURN_URL = "OpenID";
private static final String P_MODE = "gerrit.mode";
private static final String P_TOKEN = "gerrit.token";
private static final String P_REMEMBER = "gerrit.remember";
private static final String P_CLAIMED = "gerrit.claimed";
private static final int LASTID_AGE = 365 * 24 * 60 * 60; // seconds
private static final String OPENID_MODE = "openid.mode";
private static final String OMODE_CANCEL = "cancel";
private static final String SCHEMA_EMAIL =
"http://schema.openid.net/contact/email";
private static final String SCHEMA_FIRSTNAME =
"http://schema.openid.net/namePerson/first";
private static final String SCHEMA_LASTNAME =
"http://schema.openid.net/namePerson/last";
private final Provider<WebSession> webSession;
private final Provider<IdentifiedUser> identifiedUser;
private final Provider<String> urlProvider;
private final AccountManager accountManager;
private final ConsumerManager manager;
private final SelfPopulatingCache<String, List> discoveryCache;
@Inject
OpenIdServiceImpl(final Provider<WebSession> cf,
final Provider<IdentifiedUser> iu,
@CanonicalWebUrl @Nullable final Provider<String> up,
@Named("openid") final Cache<String, List> openidCache,
final AccountManager am) throws ConsumerException {
webSession = cf;
identifiedUser = iu;
urlProvider = up;
accountManager = am;
manager = new ConsumerManager();
discoveryCache = new SelfPopulatingCache<String, List>(openidCache) {
@Override
protected List createEntry(final String url) throws Exception {
try {
final List<?> list = manager.discover(url);
return list != null && !list.isEmpty() ? list : null;
} catch (DiscoveryException e) {
return null;
}
}
};
}
public void discover(final String openidIdentifier,
final SignInMode mode, final boolean remember,
final String returnToken, final AsyncCallback<DiscoveryResult> callback) {
final State state;
state = init(openidIdentifier, mode, remember, returnToken);
if (state == null) {
callback.onSuccess(new DiscoveryResult(false));
return;
}
final AuthRequest aReq;
try {
aReq = manager.authenticate(state.discovered, state.retTo.toString());
aReq.setRealm(state.contextUrl);
if (requestRegistration(aReq)) {
final SRegRequest sregReq = SRegRequest.createFetchRequest();
sregReq.addAttribute("fullname", true);
sregReq.addAttribute("email", true);
aReq.addExtension(sregReq);
final FetchRequest fetch = FetchRequest.createFetchRequest();
fetch.addAttribute("FirstName", SCHEMA_FIRSTNAME, true);
fetch.addAttribute("LastName", SCHEMA_LASTNAME, true);
fetch.addAttribute("Email", SCHEMA_EMAIL, true);
aReq.addExtension(fetch);
}
} catch (MessageException e) {
callback.onSuccess(new DiscoveryResult(false));
return;
} catch (ConsumerException e) {
callback.onSuccess(new DiscoveryResult(false));
return;
}
callback.onSuccess(new DiscoveryResult(true, aReq.getDestinationUrl(false),
aReq.getParameterMap()));
}
private boolean requestRegistration(final AuthRequest aReq) {
if (AuthRequest.SELECT_ID.equals(aReq.getIdentity())) {
// We don't know anything about the identity, as the provider
// will offer the user a way to indicate their identity. Skip
// any database query operation and assume we must ask for the
// registration information, in case the identity is new to us.
//
return true;
}
// We might already have this account on file. Look for it.
//
try {
return accountManager.lookup(aReq.getIdentity()) == null;
} catch (AccountException e) {
log.warn("Cannot determine if user account exists", e);
return true;
}
}
/** Called by {@link OpenIdLoginServlet} doGet, doPost */
void doAuth(final HttpServletRequest req, final HttpServletResponse rsp)
throws Exception {
if (OMODE_CANCEL.equals(req.getParameter(OPENID_MODE))) {
cancel(req, rsp);
return;
}
// Process the authentication response.
//
final SignInMode mode = signInMode(req);
final String openidIdentifier = req.getParameter("openid.identity");
final String claimedIdentifier = req.getParameter(P_CLAIMED);
final String returnToken = req.getParameter(P_TOKEN);
final boolean remember = "1".equals(req.getParameter(P_REMEMBER));
final String rediscoverIdentifier =
claimedIdentifier != null ? claimedIdentifier : openidIdentifier;
final State state;
state = init(rediscoverIdentifier, mode, remember, returnToken);
if (state == null) {
// Re-discovery must have failed, we can't run a login.
//
cancel(req, rsp);
return;
}
final String returnTo = req.getParameter("openid.return_to");
if (returnTo != null && returnTo.contains("openid.rpnonce=")) {
// Some providers (claimid.com) seem to embed these request
// parameters into our return_to URL, and then give us them
// in the return_to request parameter. But not all.
//
state.retTo.put("openid.rpnonce", req.getParameter("openid.rpnonce"));
state.retTo.put("openid.rpsig", req.getParameter("openid.rpsig"));
}
final VerificationResult result =
manager.verify(state.retTo.toString(), new ParameterList(req
.getParameterMap()), state.discovered);
if (result.getVerifiedId() == null /* authentication failure */) {
if ("Nonce verification failed.".equals(result.getStatusMsg())) {
// We might be suffering from clock skew on this system.
//
log.error("OpenID failure: " + result.getStatusMsg()
+ " Likely caused by clock skew on this server,"
+ " install/configure NTP.");
cancelWithError(req, rsp, result.getStatusMsg());
} else if (result.getStatusMsg() != null) {
// Authentication failed.
//
log.error("OpenID failure: " + result.getStatusMsg());
cancelWithError(req, rsp, result.getStatusMsg());
} else {
// Assume authentication was canceled.
//
cancel(req, rsp);
}
return;
}
final Message authRsp = result.getAuthResponse();
SRegResponse sregRsp = null;
FetchResponse fetchRsp = null;
if (authRsp.hasExtension(SRegMessage.OPENID_NS_SREG)) {
final MessageExtension ext =
authRsp.getExtension(SRegMessage.OPENID_NS_SREG);
if (ext instanceof SRegResponse) {
sregRsp = (SRegResponse) ext;
}
}
if (authRsp.hasExtension(AxMessage.OPENID_NS_AX)) {
final MessageExtension ext = authRsp.getExtension(AxMessage.OPENID_NS_AX);
if (ext instanceof FetchResponse) {
fetchRsp = (FetchResponse) ext;
}
}
final com.google.gerrit.server.account.AuthRequest areq =
new com.google.gerrit.server.account.AuthRequest(openidIdentifier);
if (sregRsp != null) {
areq.setDisplayName(sregRsp.getAttributeValue("fullname"));
areq.setEmailAddress(sregRsp.getAttributeValue("email"));
} else if (fetchRsp != null) {
final String firstName = fetchRsp.getAttributeValue("FirstName");
final String lastName = fetchRsp.getAttributeValue("LastName");
final StringBuilder n = new StringBuilder();
if (firstName != null && firstName.length() > 0) {
n.append(firstName);
}
if (lastName != null && lastName.length() > 0) {
if (n.length() > 0) {
n.append(' ');
}
n.append(lastName);
}
areq.setDisplayName(n.length() > 0 ? n.toString() : null);
areq.setEmailAddress(fetchRsp.getAttributeValue("Email"));
}
if (claimedIdentifier != null) {
// The user used a claimed identity which has delegated to the verified
// identity we have in our AuthRequest above. We still should have a
// link between the two, so set one up if not present.
//
Account.Id claimedId = accountManager.lookup(claimedIdentifier);
Account.Id actualId = accountManager.lookup(areq.getExternalId());
if (claimedId != null && actualId != null) {
if (claimedId.equals(actualId)) {
// Both link to the same account, that's what we expected.
} else {
// This is (for now) a fatal error. There are two records
// for what might be the same user.
//
log.error("OpenID accounts disagree over user identity:\n"
+ " Claimed ID: " + claimedId + " is " + claimedIdentifier
+ "\n" + " Delgate ID: " + actualId + " is "
+ areq.getExternalId());
cancelWithError(req, rsp, "Contact site administrator");
return;
}
} else if (claimedId == null && actualId != null) {
// Older account, the actual was already created but the claimed
// was missing due to a bug in Gerrit. Link the claimed.
//
final com.google.gerrit.server.account.AuthRequest linkReq =
new com.google.gerrit.server.account.AuthRequest(claimedIdentifier);
linkReq.setDisplayName(areq.getDisplayName());
linkReq.setEmailAddress(areq.getEmailAddress());
accountManager.link(actualId, linkReq);
} else if (claimedId != null && actualId == null) {
// Claimed account already exists, but it smells like the user has
// changed their delegate to point to a different provider. Link
// the new provider.
//
accountManager.link(claimedId, areq);
} else {
// Both are null, we are going to create a new account below.
}
}
try {
switch (mode) {
case REGISTER:
case SIGN_IN:
final com.google.gerrit.server.account.AuthResult arsp;
arsp = accountManager.authenticate(areq);
final Cookie lastId = new Cookie(OpenIdUrls.LASTID_COOKIE, "");
lastId.setPath(req.getContextPath() + "/");
if (remember) {
lastId.setValue(rediscoverIdentifier);
lastId.setMaxAge(LASTID_AGE);
} else {
lastId.setMaxAge(0);
}
rsp.addCookie(lastId);
webSession.get().login(arsp.getAccountId(), remember);
if (arsp.isNew() && claimedIdentifier != null) {
final com.google.gerrit.server.account.AuthRequest linkReq =
new com.google.gerrit.server.account.AuthRequest(
claimedIdentifier);
linkReq.setDisplayName(areq.getDisplayName());
linkReq.setEmailAddress(areq.getEmailAddress());
accountManager.link(arsp.getAccountId(), linkReq);
}
callback(arsp.isNew(), req, rsp);
break;
case LINK_IDENTIY:
accountManager.link(identifiedUser.get().getAccountId(), areq);
callback(false, req, rsp);
break;
}
} catch (AccountException e) {
log.error("OpenID authentication failure", e);
cancelWithError(req, rsp, "Contact site administrator");
}
}
private boolean isSignIn(final SignInMode mode) {
switch (mode) {
case SIGN_IN:
case REGISTER:
return true;
default:
return false;
}
}
private static SignInMode signInMode(final HttpServletRequest req) {
try {
return SignInMode.valueOf(req.getParameter(P_MODE));
} catch (RuntimeException e) {
return SignInMode.SIGN_IN;
}
}
private void callback(final boolean isNew, final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
String token = req.getParameter(P_TOKEN);
if (token == null || token.isEmpty() || token.startsWith("SignInFailure,")) {
token = PageLinks.MINE;
}
final StringBuilder rdr = new StringBuilder();
rdr.append(urlProvider.get());
rdr.append('#');
if (isNew && !token.startsWith(PageLinks.REGISTER + ",")) {
rdr.append(PageLinks.REGISTER);
rdr.append(',');
}
rdr.append(token);
rsp.sendRedirect(rdr.toString());
}
private void cancel(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
if (isSignIn(signInMode(req))) {
webSession.get().logout();
}
callback(false, req, rsp);
}
private void cancelWithError(final HttpServletRequest req,
final HttpServletResponse rsp, final String errorDetail)
throws IOException {
final SignInMode mode = signInMode(req);
if (isSignIn(mode)) {
webSession.get().logout();
}
final StringBuilder rdr = new StringBuilder();
rdr.append(urlProvider.get());
rdr.append('#');
rdr.append("SignInFailure");
rdr.append(',');
rdr.append(mode.name());
rdr.append(',');
rdr.append(errorDetail != null ? KeyUtil.encode(errorDetail) : "");
rsp.sendRedirect(rdr.toString());
}
private State init(final String openidIdentifier,
final SignInMode mode, final boolean remember,
final String returnToken) {
final List<?> list = discoveryCache.get(openidIdentifier);
if (list == null || list.isEmpty()) {
return null;
}
final String contextUrl = urlProvider.get();
final DiscoveryInformation discovered = manager.associate(list);
final UrlEncoded retTo = new UrlEncoded(contextUrl + RETURN_URL);
retTo.put(P_MODE, mode.name());
if (returnToken != null && returnToken.length() > 0) {
retTo.put(P_TOKEN, returnToken);
}
if (remember) {
retTo.put(P_REMEMBER, "1");
}
if (discovered.hasClaimedIdentifier()) {
retTo.put(P_CLAIMED, discovered.getClaimedIdentifier().getIdentifier());
}
return new State(discovered, retTo, contextUrl);
}
private static class State {
final DiscoveryInformation discovered;
final UrlEncoded retTo;
final String contextUrl;
State(final DiscoveryInformation d, final UrlEncoded r, final String c) {
discovered = d;
retTo = r;
contextUrl = c;
}
}
}

View File

@@ -0,0 +1,326 @@
// 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.raw;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.FileTypeRegistry;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import eu.medsea.mimeutil.MimeType;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.NB;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Exports a single version of a patch as a normal file download.
* <p>
* This can be relatively unsafe with Microsoft Internet Explorer 6.0 as the
* browser will (rather incorrectly) treat an HTML or JavaScript file its
* supposed to download as though it was served by this site, and will execute
* it with the site's own protection domain. This opens a massive security hole
* so we package the content into a zip file.
*/
@SuppressWarnings("serial")
@Singleton
public class CatServlet extends HttpServlet {
private static final MimeType ZIP = new MimeType("application/zip");
private final Provider<ReviewDb> requestDb;
private final GitRepositoryManager repoManager;
private final SecureRandom rng;
private final FileTypeRegistry registry;
private final ChangeControl.Factory changeControl;
@Inject
CatServlet(final GitRepositoryManager grm, final Provider<ReviewDb> sf,
final FileTypeRegistry ftr, final ChangeControl.Factory ccf) {
requestDb = sf;
repoManager = grm;
rng = new SecureRandom();
registry = ftr;
changeControl = ccf;
}
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
String keyStr = req.getPathInfo();
// We shouldn't have to do this extra decode pass, but somehow we
// are now receiving our "^1" suffix as "%5E1", which confuses us
// downstream. Other times we get our embedded "," as "%2C", which
// is equally bad. And yet when these happen a "%2F" is left as-is,
// rather than escaped as "%252F", which makes me feel really really
// uncomfortable with a blind decode right here.
//
keyStr = URLDecoder.decode(keyStr, "UTF-8");
if (!keyStr.startsWith("/")) {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
keyStr = keyStr.substring(1);
final Patch.Key patchKey;
final int side;
{
final int c = keyStr.lastIndexOf('^');
if (c == 0) {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
if (c < 0) {
side = 0;
} else {
try {
side = Integer.parseInt(keyStr.substring(c + 1));
keyStr = keyStr.substring(0, c);
} catch (NumberFormatException e) {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
}
try {
patchKey = Patch.Key.parse(keyStr);
} catch (NumberFormatException e) {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
}
final Change.Id changeId = patchKey.getParentKey().getParentKey();
final Project project;
final PatchSet patchSet;
try {
final ReviewDb db = requestDb.get();
final ChangeControl control = changeControl.validateFor(changeId);
project = control.getProject();
patchSet = db.patchSets().get(patchKey.getParentKey());
if (patchSet == null) {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
} catch (NoSuchChangeException e) {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
} catch (OrmException e) {
getServletContext().log("Cannot query database", e);
rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
final Repository repo;
try {
repo = repoManager.openRepository(project.getNameKey().get());
} catch (RepositoryNotFoundException e) {
getServletContext().log("Cannot open repository", e);
rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
final byte[] blobData;
final RevCommit fromCommit;
final String suffix;
final String path = patchKey.getFileName();
try {
final RevWalk rw = new RevWalk(repo);
final RevCommit c;
final TreeWalk tw;
c = rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
if (side == 0) {
fromCommit = c;
suffix = "new";
} else if (1 <= side && side - 1 < c.getParentCount()) {
fromCommit = rw.parseCommit(c.getParent(side - 1));
if (c.getParentCount() == 1) {
suffix = "old";
} else {
suffix = "old" + side;
}
} else {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
tw = TreeWalk.forPath(repo, path, fromCommit.getTree());
if (tw == null) {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
if (tw.getFileMode(0).getObjectType() == Constants.OBJ_BLOB) {
blobData = repo.openBlob(tw.getObjectId(0)).getCachedBytes();
} else {
rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
} catch (IOException e) {
getServletContext().log("Cannot read repository", e);
rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
} catch (RuntimeException e) {
getServletContext().log("Cannot read repository", e);
rsp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
} finally {
repo.close();
}
final long when = fromCommit.getCommitTime() * 1000L;
MimeType contentType = registry.getMimeType(path, blobData);
final byte[] outData;
if (registry.isSafeInline(contentType)) {
outData = blobData;
} else {
// The content may not be safe to transmit inline, as a browser might
// interpret it as HTML or JavaScript hosted by this site. Such code
// might then run in the site's security domain, and may be able to use
// the user's cookies to perform unauthorized actions.
//
// Usually, wrapping the content into a ZIP file forces the browser to
// save the content to the local system instead.
//
final ByteArrayOutputStream zip = new ByteArrayOutputStream();
final ZipOutputStream zo = new ZipOutputStream(zip);
final ZipEntry e = new ZipEntry(safeFileName(path, rand(req, suffix)));
e.setComment(fromCommit.name() + ":" + path);
e.setSize(blobData.length);
e.setTime(when);
zo.putNextEntry(e);
zo.write(blobData);
zo.closeEntry();
zo.close();
outData = zip.toByteArray();
contentType = ZIP;
rsp.setHeader("Content-Disposition", "attachment; filename=\""
+ safeFileName(path, suffix) + ".zip" + "\"");
}
rsp.setContentType(contentType.toString());
rsp.setContentLength(outData.length);
rsp.setDateHeader("Last-Modified", when);
rsp.setDateHeader("Expires", 0L);
rsp.setHeader("Pragma", "no-cache");
rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
rsp.getOutputStream().write(outData);
}
private static String safeFileName(String fileName, final String suffix) {
// Convert a file path (e.g. "src/Init.c") to a safe file name with
// no meta-characters that might be unsafe on any given platform.
//
final int slash = fileName.lastIndexOf('/');
if (slash >= 0) {
fileName = fileName.substring(slash + 1);
}
final StringBuilder r = new StringBuilder(fileName.length());
for (int i = 0; i < fileName.length(); i++) {
final char c = fileName.charAt(i);
if (c == '_' || c == '-' || c == '.' || c == '@') {
r.append(c);
} else if ('0' <= c && c <= '9') {
r.append(c);
} else if ('A' <= c && c <= 'Z') {
r.append(c);
} else if ('a' <= c && c <= 'z') {
r.append(c);
} else if (c == ' ' || c == '\n' || c == '\r' || c == '\t') {
r.append('-');
} else {
r.append('_');
}
}
fileName = r.toString();
final int ext = fileName.lastIndexOf('.');
if (ext <= 0) {
return fileName + "_" + suffix;
} else {
return fileName.substring(0, ext) + "_" + suffix
+ fileName.substring(ext);
}
}
private String rand(final HttpServletRequest req, final String suffix)
throws UnsupportedEncodingException {
// Produce a random suffix that is difficult (or nearly impossible)
// for an attacker to guess in advance. This reduces the risk that
// an attacker could upload a *.class file and have us send a ZIP
// that can be invoked through an applet tag in the victim's browser.
//
final MessageDigest md = Constants.newMessageDigest();
final byte[] buf = new byte[8];
NB.encodeInt32(buf, 0, req.getRemotePort());
md.update(req.getRemoteAddr().getBytes("UTF-8"));
md.update(buf, 0, 4);
NB.encodeInt64(buf, 0, System.currentTimeMillis());
md.update(buf, 0, 8);
rng.nextBytes(buf);
md.update(buf, 0, 8);
return suffix + "-" + ObjectId.fromRaw(md.digest()).name();
}
}

View File

@@ -0,0 +1,27 @@
<html>
<head>
<title>Gerrit Code Review</title>
<meta name="gwt:property" content="locale=en_US" />
<script id="gerrit_hostpagedata"></script>
<script id="gerrit_module" type="text/javascript" language="javascript" src="gerrit/gerrit.nocache.js"></script>
<script src="prettify/20090521.js" type="text/javascript" language="javascript" defer="true"></script>
<style id="gerrit_sitecss" type="text/css"></style>
<link rel="icon" type="image/gif" href="favicon.ico" />
</head>
<body>
<div id="gerrit_topmenu"></div>
<div id="gerrit_header"></div>
<div id="gerrit_startinggerrit" style="margin-left: 10px;">
<p>Loading <a href="http://code.google.com/p/gerrit/" target="_blank">Gerrit Code Review</a> ...</p>
<noscript>
<p>Gerrit requires a JavaScript enabled browser.</p>
</noscript>
</div>
<div id="gerrit_body"></div>
<div id="gerrit_pagefooter">
<div id="gerrit_footer"></div>
<div id="gerrit_btmmenu"></div>
</div>
<iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
</body>
</html>

View File

@@ -0,0 +1,249 @@
// 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.httpd.raw;
import com.google.gerrit.common.data.GerritConfig;
import com.google.gerrit.common.data.HostPageData;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.Nullable;
import com.google.gerrit.server.config.SitePath;
import com.google.gwt.user.server.rpc.RPCServletUtils;
import com.google.gwtjsonrpc.server.JsonServlet;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.security.MessageDigest;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** Sends the Gerrit host page to clients. */
@SuppressWarnings("serial")
@Singleton
public class HostPageServlet extends HttpServlet {
private final Provider<CurrentUser> currentUser;
private final File sitePath;
private final GerritConfig config;
private final Provider<String> urlProvider;
private final boolean wantSSL;
private final Document hostDoc;
@Inject
HostPageServlet(final Provider<CurrentUser> cu, @SitePath final File path,
final GerritConfig gc,
@CanonicalWebUrl @Nullable final Provider<String> up,
@CanonicalWebUrl @Nullable final String configuredUrl,
final ServletContext servletContext) throws IOException {
currentUser = cu;
urlProvider = up;
sitePath = path;
config = gc;
wantSSL = configuredUrl != null && configuredUrl.startsWith("https:");
final String pageName = "HostPage.html";
hostDoc = HtmlDomUtil.parseFile(getClass(), pageName);
if (hostDoc == null) {
throw new FileNotFoundException("No " + pageName + " in webapp");
}
fixModuleReference(hostDoc, servletContext);
injectCssFile(hostDoc, "gerrit_sitecss", sitePath, "GerritSite.css");
injectXmlFile(hostDoc, "gerrit_header", sitePath, "GerritSiteHeader.html");
injectXmlFile(hostDoc, "gerrit_footer", sitePath, "GerritSiteFooter.html");
}
private void injectXmlFile(final Document hostDoc, final String id,
final File sitePath, final String fileName) throws IOException {
final Element banner = HtmlDomUtil.find(hostDoc, id);
if (banner == null) {
return;
}
while (banner.getFirstChild() != null) {
banner.removeChild(banner.getFirstChild());
}
final Document html = HtmlDomUtil.parseFile(sitePath, fileName);
if (html == null) {
banner.getParentNode().removeChild(banner);
return;
}
final Element content = html.getDocumentElement();
banner.appendChild(hostDoc.importNode(content, true));
}
private void injectCssFile(final Document hostDoc, final String id,
final File sitePath, final String fileName) throws IOException {
final Element banner = HtmlDomUtil.find(hostDoc, id);
if (banner == null) {
return;
}
while (banner.getFirstChild() != null) {
banner.removeChild(banner.getFirstChild());
}
final String css = HtmlDomUtil.readFile(sitePath, fileName);
if (css == null) {
banner.getParentNode().removeChild(banner);
return;
}
banner.removeAttribute("id");
banner.appendChild(hostDoc.createCDATASection("\n" + css + "\n"));
}
private void injectJson(final Document hostDoc, final String id,
final Object obj) {
final Element scriptNode = HtmlDomUtil.find(hostDoc, id);
if (scriptNode == null) {
return;
}
while (scriptNode.getFirstChild() != null) {
scriptNode.removeChild(scriptNode.getFirstChild());
}
if (obj == null) {
scriptNode.getParentNode().removeChild(scriptNode);
return;
}
final StringWriter w = new StringWriter();
w.write("<!--\n");
w.write("var ");
w.write(id);
w.write("_obj=");
JsonServlet.defaultGsonBuilder().create().toJson(obj, w);
w.write(";\n// -->\n");
scriptNode.removeAttribute("id");
scriptNode.setAttribute("type", "text/javascript");
scriptNode.setAttribute("language", "javascript");
scriptNode.appendChild(hostDoc.createCDATASection(w.toString()));
}
private void fixModuleReference(final Document hostDoc,
final ServletContext servletContext) throws IOException {
final Element scriptNode = HtmlDomUtil.find(hostDoc, "gerrit_module");
if (scriptNode == null) {
throw new IOException("No gerrit_module to rewrite in host document");
}
scriptNode.removeAttribute("id");
final String src = scriptNode.getAttribute("src");
InputStream in = servletContext.getResourceAsStream("/" + src);
if (in == null) {
throw new IOException("No " + src + " in webapp root");
}
final MessageDigest md = Constants.newMessageDigest();
try {
try {
final byte[] buf = new byte[1024];
int n;
while ((n = in.read(buf)) > 0) {
md.update(buf, 0, n);
}
} finally {
in.close();
}
} catch (IOException e) {
throw new IOException("Failed reading " + src, e);
}
final String vstr = ObjectId.fromRaw(md.digest()).name();
scriptNode.setAttribute("src", src + "?content=" + vstr);
}
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
// If we wanted SSL, but the user didn't come to us over an SSL channel,
// force it to be SSL by issuing a protocol redirect. Try to keep the
// name "localhost" in case this is an SSH port tunnel.
//
if (wantSSL && !isSecure(req)) {
final StringBuffer reqUrl = req.getRequestURL();
if (isLocalHost(req)) {
reqUrl.replace(0, reqUrl.indexOf(":"), "https");
} else {
reqUrl.setLength(0);
reqUrl.append(urlProvider.get());
}
rsp.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
rsp.setHeader("Location", reqUrl.toString());
return;
}
final HostPageData pageData = new HostPageData();
pageData.config = config;
final CurrentUser user = currentUser.get();
if (user instanceof IdentifiedUser) {
pageData.userAccount = ((IdentifiedUser) user).getAccount();
}
final Document peruser = HtmlDomUtil.clone(hostDoc);
injectJson(peruser, "gerrit_hostpagedata", pageData);
final byte[] raw = HtmlDomUtil.toUTF8(peruser);
final byte[] tosend;
if (RPCServletUtils.acceptsGzipEncoding(req)) {
rsp.setHeader("Content-Encoding", "gzip");
tosend = HtmlDomUtil.compress(raw);
} else {
tosend = raw;
}
rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
rsp.setHeader("Pragma", "no-cache");
rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
rsp.setContentType("text/html");
rsp.setCharacterEncoding(HtmlDomUtil.ENC);
rsp.setContentLength(tosend.length);
final OutputStream out = rsp.getOutputStream();
try {
out.write(tosend);
} finally {
out.close();
}
}
private static boolean isSecure(final HttpServletRequest req) {
return "https".equals(req.getScheme()) || req.isSecure();
}
private static boolean isLocalHost(final HttpServletRequest req) {
return "localhost".equals(req.getServerName())
|| "127.0.0.1".equals(req.getServerName());
}
}

View File

@@ -0,0 +1,27 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html>
<head>
<title>Gerrit Code Review</title>
<script type="text/javascript" language="javascript">
var href = window.location.href;
var p = href.indexOf('#');
var token = '';
if (p >= 0) {
token = href.substring(p);
href = href.substring(0, p);
}
var r = '/Gerrit';
if (href.length >= r.length
&& href.substring(href.length - r.length) == r) {
href = href.substring(0, href.length - r.length + 1) + token;
} else {
href = '.' + token;
}
window.location.replace(href);
</script>
</head>
<body>
<p>Redirecting to <a href=".">Gerrit Code Review</a>.</p>
</body>
</html>

View File

@@ -0,0 +1,82 @@
// 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.raw;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gwt.user.server.rpc.RPCServletUtils;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Redirects from {@code /Gerrit#foo} to {@code /#foo} in JavaScript.
* <p>
* This redirect exists to convert the older /Gerrit URL into the more modern
* URL format which does not use a servlet name for the host page. We cannot do
* the redirect here in the server side, as it would lose any history token that
* appears in the URL. Instead we send an HTML page which instructs the browser
* to replace the URL, but preserve the history token.
*/
@SuppressWarnings("serial")
@Singleton
public class LegacyGerritServlet extends HttpServlet {
private final byte[] raw;
private final byte[] compressed;
@Inject
LegacyGerritServlet(final ServletContext servletContext) throws IOException {
final String pageName = "LegacyGerrit.html";
final String doc = HtmlDomUtil.readFile(getClass(), pageName);
if (doc == null) {
throw new FileNotFoundException("No " + pageName + " in webapp");
}
raw = doc.getBytes(HtmlDomUtil.ENC);
compressed = HtmlDomUtil.compress(raw);
}
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
final byte[] tosend;
if (RPCServletUtils.acceptsGzipEncoding(req)) {
rsp.setHeader("Content-Encoding", "gzip");
tosend = compressed;
} else {
tosend = raw;
}
rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
rsp.setHeader("Pragma", "no-cache");
rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
rsp.setContentType("text/html");
rsp.setCharacterEncoding(HtmlDomUtil.ENC);
rsp.setContentLength(tosend.length);
final OutputStream out = rsp.getOutputStream();
try {
out.write(tosend);
} finally {
out.close();
}
}
}

View File

@@ -0,0 +1,90 @@
// 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.raw;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Singleton
public class PrettifyServlet extends HttpServlet {
private static final String VERSION = "20090521";
private final byte[] content;
@Inject
PrettifyServlet(final ServletContext servletContext) throws IOException {
final String myDir = "/gerrit/prettify" + VERSION + "/";
final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
load(buffer, servletContext, myDir + "prettify.js");
for (Object p : servletContext.getResourcePaths(myDir)) {
String name = (String) p;
if (name.startsWith(myDir + "lang-") && name.endsWith(".js")) {
load(buffer, servletContext, name);
}
}
content = buffer.toByteArray();
}
private void load(final OutputStream buffer,
final ServletContext servletContext, final String path)
throws IOException {
final InputStream in = servletContext.getResourceAsStream(path);
if (in != null) {
try {
final byte[] tmp = new byte[4096];
int cnt;
while ((cnt = in.read(tmp)) > 0) {
buffer.write(tmp, 0, cnt);
}
buffer.write(';');
buffer.write('\n');
in.close();
} catch (IOException e) {
throw new IOException("Cannot read " + path, e);
}
}
}
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
final String want = req.getPathInfo();
if (want.equals("/" + VERSION + ".js")) {
final long now = System.currentTimeMillis();
rsp.setHeader("Cache-Control", "max-age=31536000,public");
rsp.setDateHeader("Expires", now + 31536000000L);
rsp.setDateHeader("Date", now);
rsp.setContentType("application/x-javascript");
rsp.setContentLength(content.length);
rsp.getOutputStream().write(content);
} else {
rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
rsp.setHeader("Pragma", "no-cache");
rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
rsp.setDateHeader("Date", System.currentTimeMillis());
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
}

View File

@@ -0,0 +1,100 @@
// 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.httpd.raw;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.jcraft.jsch.HostKey;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet hosting an SSH daemon on another port. During a standard HTTP GET
* request the servlet returns the hostname and port number back to the client
* in the form <code>${host} ${port}</code>.
* <p>
* Use a Git URL such as <code>ssh://${email}@${host}:${port}/${path}</code>,
* e.g. <code>ssh://sop@google.com@gerrit.com:8010/tools/gerrit.git</code> to
* access the SSH daemon itself.
* <p>
* Versions of Git before 1.5.3 may require setting the username and port
* properties in the user's <code>~/.ssh/config</code> file, and using a host
* alias through a URL such as <code>gerrit-alias:/tools/gerrit.git:
* <pre>
* Host gerrit-alias
* User sop@google.com
* Hostname gerrit.com
* Port 8010
* </pre>
*/
@SuppressWarnings("serial")
@Singleton
public class SshInfoServlet extends HttpServlet {
private final SshInfo sshd;
@Inject
SshInfoServlet(final SshInfo daemon) {
sshd = daemon;
}
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
rsp.setHeader("Pragma", "no-cache");
rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
final List<HostKey> hostKeys = sshd.getHostKeys();
final String out;
if (!hostKeys.isEmpty()) {
String host = hostKeys.get(0).getHost();
String port = "22";
if (host.contains(":")) {
final int p = host.lastIndexOf(':');
port = host.substring(p + 1);
host = host.substring(0, p);
}
if (host.equals("*")) {
host = req.getServerName();
} else if (host.startsWith("[") && host.endsWith("]")) {
host = host.substring(1, host.length() - 1);
}
out = host + " " + port;
} else {
out = "NOT_AVAILABLE";
}
rsp.setCharacterEncoding("UTF-8");
rsp.setContentType("text/plain");
final PrintWriter w = rsp.getWriter();
try {
w.write(out);
} finally {
w.close();
}
}
}

View File

@@ -0,0 +1,157 @@
// 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.httpd.raw;
import com.google.gerrit.server.config.SitePath;
import com.google.gwt.user.server.rpc.RPCServletUtils;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jgit.util.NB;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.zip.GZIPOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** Sends static content from the site 's <code>static/</code> subdirectory. */
@SuppressWarnings("serial")
@Singleton
public class StaticServlet extends HttpServlet {
private static final long MAX_AGE = 12 * 60 * 60 * 1000L/* milliseconds */;
private static final String CACHE_CTRL =
"public, max-age=" + (MAX_AGE / 1000L);
private static final HashMap<String, String> MIME_TYPES =
new HashMap<String, String>();
static {
MIME_TYPES.put("html", "text/html");
MIME_TYPES.put("htm", "text/html");
MIME_TYPES.put("js", "application/x-javascript");
MIME_TYPES.put("css", "text/css");
MIME_TYPES.put("rtf", "text/rtf");
MIME_TYPES.put("txt", "text/plain");
MIME_TYPES.put("text", "text/plain");
MIME_TYPES.put("pdf", "application/pdf");
MIME_TYPES.put("jpeg", "image/jpeg");
MIME_TYPES.put("jpg", "image/jpeg");
MIME_TYPES.put("gif", "image/gif");
MIME_TYPES.put("png", "image/png");
MIME_TYPES.put("tiff", "image/tiff");
MIME_TYPES.put("tif", "image/tiff");
MIME_TYPES.put("svg", "image/svg+xml");
}
private static String contentType(final String name) {
final int dot = name.lastIndexOf('.');
final String ext = 0 < dot ? name.substring(dot + 1) : "";
final String type = MIME_TYPES.get(ext);
return type != null ? type : "application/octet-stream";
}
private static byte[] readFile(final File p) throws IOException {
final FileInputStream in = new FileInputStream(p);
try {
final byte[] r = new byte[(int) in.getChannel().size()];
NB.readFully(in, r, 0, r.length);
return r;
} finally {
in.close();
}
}
private static byte[] compress(final byte[] raw) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final GZIPOutputStream gz = new GZIPOutputStream(out);
gz.write(raw);
gz.finish();
gz.flush();
return out.toByteArray();
}
private final File staticBase;
@Inject
StaticServlet(@SitePath final File sitePath) {
staticBase = new File(sitePath, "static");
}
private File local(final HttpServletRequest req) {
final String name = req.getPathInfo();
if (name.length() < 2 || !name.startsWith("/")) {
// Too short to be a valid file name, or doesn't start with
// the path info separator like we expected.
//
return null;
}
if (name.indexOf('/', 1) > 0 || name.indexOf('\\', 1) > 0) {
// Contains a path separator. Don't serve it as the client
// might be trying something evil like "/../../etc/passwd".
// This static servlet is just meant to facilitate simple
// assets like banner images.
//
return null;
}
final File p = new File(staticBase, name.substring(1));
return p.isFile() ? p : null;
}
@Override
protected long getLastModified(final HttpServletRequest req) {
final File p = local(req);
return p != null ? p.lastModified() : -1;
}
@Override
protected void doGet(final HttpServletRequest req,
final HttpServletResponse rsp) throws IOException {
final File p = local(req);
if (p == null) {
rsp.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
final String type = contentType(p.getName());
final byte[] tosend;
if (!type.equals("application/x-javascript")
&& RPCServletUtils.acceptsGzipEncoding(req)) {
rsp.setHeader("Content-Encoding", "gzip");
tosend = compress(readFile(p));
} else {
tosend = readFile(p);
}
rsp.setHeader("Cache-Control", CACHE_CTRL);
rsp.setDateHeader("Expires", System.currentTimeMillis() + MAX_AGE);
rsp.setDateHeader("Last-Modified", p.lastModified());
rsp.setContentType(type);
rsp.setContentLength(tosend.length);
final OutputStream out = rsp.getOutputStream();
try {
out.write(tosend);
} finally {
out.close();
}
}
}

View File

@@ -0,0 +1,123 @@
// 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.httpd.rpc;
import com.google.gerrit.common.errors.CorruptEntityException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.NoSuchGroupException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Provider;
/** Support for services which require a {@link ReviewDb} instance. */
public class BaseServiceImplementation {
private final Provider<ReviewDb> schema;
private final Provider<? extends CurrentUser> currentUser;
protected BaseServiceImplementation(final Provider<ReviewDb> schema,
final Provider<? extends CurrentUser> currentUser) {
this.schema = schema;
this.currentUser = currentUser;
}
@Deprecated
protected Account.Id getAccountId() {
CurrentUser u = currentUser.get();
if (u instanceof IdentifiedUser) {
return ((IdentifiedUser) u).getAccountId();
}
return null;
}
/**
* Executes <code>action.run</code> with an active ReviewDb connection.
* <p>
* A database handle is automatically opened and closed around the action's
* {@link Action#run(ReviewDb)} method. OrmExceptions are caught and passed
* into the onFailure method of the callback.
*
* @param <T> type of result the callback expects.
* @param callback the callback that will receive the result.
* @param action the action logic to perform.
*/
protected <T> void run(final AsyncCallback<T> callback, final Action<T> action) {
try {
final T r = action.run(schema.get());
if (r != null) {
callback.onSuccess(r);
}
} catch (NoSuchProjectException e) {
callback.onFailure(new NoSuchEntityException());
} catch (NoSuchGroupException e) {
callback.onFailure(new NoSuchEntityException());
} catch (OrmException e) {
if (e.getCause() instanceof Failure) {
callback.onFailure(e.getCause().getCause());
} else if (e.getCause() instanceof CorruptEntityException) {
callback.onFailure(e.getCause());
} else if (e.getCause() instanceof NoSuchEntityException) {
callback.onFailure(e.getCause());
} else {
callback.onFailure(e);
}
} catch (Failure e) {
if (e.getCause() instanceof NoSuchProjectException
|| e.getCause() instanceof NoSuchChangeException
|| e.getCause() instanceof NoSuchGroupException) {
callback.onFailure(new NoSuchEntityException());
} else {
callback.onFailure(e.getCause());
}
}
}
/** Exception whose cause is passed into onFailure. */
@Deprecated
public static class Failure extends Exception {
private static final long serialVersionUID = 1L;
public Failure(final Throwable why) {
super(why);
}
}
/** Arbitrary action to run with a database connection. */
public static interface Action<T> {
/**
* Perform this action, returning the onSuccess value.
*
* @param db an open database handle to be used by this connection.
* @return he value to pass to {@link AsyncCallback#onSuccess(Object)}.
* @throws OrmException any schema based action failed.
* @throws Failure cause is given to
* {@link AsyncCallback#onFailure(Throwable)}.
* @throws NoSuchProjectException
* @throws NoSuchGroupException
*/
T run(ReviewDb db) throws OrmException, Failure, NoSuchProjectException,
NoSuchGroupException;
}
}

View File

@@ -0,0 +1,598 @@
// 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.httpd.rpc;
import com.google.gerrit.common.data.AccountDashboardInfo;
import com.google.gerrit.common.data.ChangeInfo;
import com.google.gerrit.common.data.ChangeListService;
import com.google.gerrit.common.data.SingleListChangeInfo;
import com.google.gerrit.common.data.ToggleStarRequest;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeAccess;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RevId;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.StarredChange;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.ResultSet;
import com.google.gwtorm.client.Transaction;
import com.google.gwtorm.client.impl.ListResultSet;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ChangeListServiceImpl extends BaseServiceImplementation implements
ChangeListService {
private static final Comparator<ChangeInfo> ID_COMP =
new Comparator<ChangeInfo>() {
public int compare(final ChangeInfo o1, final ChangeInfo o2) {
return o1.getId().get() - o2.getId().get();
}
};
private static final Comparator<ChangeInfo> SORT_KEY_COMP =
new Comparator<ChangeInfo>() {
public int compare(final ChangeInfo o1, final ChangeInfo o2) {
return o2.getSortKey().compareTo(o1.getSortKey());
}
};
private static final Comparator<Change> QUERY_PREV =
new Comparator<Change>() {
public int compare(final Change a, final Change b) {
return a.getSortKey().compareTo(b.getSortKey());
}
};
private static final Comparator<Change> QUERY_NEXT =
new Comparator<Change>() {
public int compare(final Change a, final Change b) {
return b.getSortKey().compareTo(a.getSortKey());
}
};
private static final int MAX_PER_PAGE = 100;
private static int safePageSize(final int pageSize) {
return 0 < pageSize && pageSize <= MAX_PER_PAGE ? pageSize : MAX_PER_PAGE;
}
private final Provider<CurrentUser> currentUser;
private final ChangeControl.Factory changeControlFactory;
private final AccountInfoCacheFactory.Factory accountInfoCacheFactory;
@Inject
ChangeListServiceImpl(final Provider<ReviewDb> schema,
final Provider<CurrentUser> currentUser,
final ChangeControl.Factory changeControlFactory,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory) {
super(schema, currentUser);
this.currentUser = currentUser;
this.changeControlFactory = changeControlFactory;
this.accountInfoCacheFactory = accountInfoCacheFactory;
}
private boolean canRead(final Change c) {
try {
return changeControlFactory.controlFor(c).isVisible();
} catch (NoSuchChangeException e) {
return false;
}
}
public void allOpenPrev(final String pos, final int pageSize,
final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new QueryPrev(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
throws OrmException {
return db.changes().allOpenPrev(sortKey, slim);
}
});
}
public void allOpenNext(final String pos, final int pageSize,
final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new QueryNext(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
throws OrmException {
return db.changes().allOpenNext(sortKey, slim);
}
});
}
public void byProjectOpenPrev(final Project.NameKey project,
final String pos, final int pageSize,
final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new QueryPrev(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
throws OrmException {
return db.changes().byProjectOpenPrev(project, sortKey, slim);
}
});
}
public void byProjectOpenNext(final Project.NameKey project,
final String pos, final int pageSize,
final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new QueryNext(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
throws OrmException {
return db.changes().byProjectOpenNext(project, sortKey, slim);
}
});
}
public void byProjectClosedPrev(final Project.NameKey project,
final Change.Status s, final String pos, final int pageSize,
final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new QueryPrev(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
throws OrmException {
return db.changes().byProjectClosedPrev(s.getCode(), project, sortKey,
slim);
}
});
}
public void byProjectClosedNext(final Project.NameKey project,
final Change.Status s, final String pos, final int pageSize,
final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new QueryNext(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int slim, String sortKey)
throws OrmException {
return db.changes().byProjectClosedNext(s.getCode(), project, sortKey,
slim);
}
});
}
public void allClosedPrev(final Change.Status s, final String pos,
final int pageSize, final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new QueryPrev(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int lim, String key)
throws OrmException {
return db.changes().allClosedPrev(s.getCode(), key, lim);
}
});
}
public void allClosedNext(final Change.Status s, final String pos,
final int pageSize, final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new QueryNext(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int lim, String key)
throws OrmException {
return db.changes().allClosedNext(s.getCode(), key, lim);
}
});
}
@Override
public void allQueryPrev(final String query, final String pos,
final int pageSize, final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new QueryPrev(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int lim, String key)
throws OrmException {
return searchQuery(db, query, lim, key, QUERY_PREV);
}
});
}
@Override
public void allQueryNext(final String query, final String pos,
final int pageSize, final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new QueryNext(pageSize, pos) {
@Override
ResultSet<Change> query(ReviewDb db, int lim, String key)
throws OrmException {
return searchQuery(db, query, lim, key, QUERY_NEXT);
}
});
}
private ResultSet<Change> searchQuery(final ReviewDb db, String query,
final int limit, final String key, final Comparator<Change> cmp)
throws OrmException {
List<Change> result = new ArrayList<Change>();
final HashSet<Change.Id> want = new HashSet<Change.Id>();
query = query.trim();
if (query.matches("^[1-9][0-9]*$")) {
want.add(Change.Id.parse(query));
} else if (query.matches("^[iI][0-9a-f]{4,}.*$")) {
if (query.startsWith("i")) {
query = "I" + query.substring(1);
}
final Change.Key a = new Change.Key(query);
final Change.Key b = a.max();
filterBySortKey(result, db.changes().byKeyRange(a, b), cmp, key);
Collections.sort(result, cmp);
if (limit < result.size()) {
result = result.subList(0, limit);
}
} else if (query.matches("^([0-9a-fA-F]{4," + RevId.LEN + "})$")) {
final RevId id = new RevId(query);
final ResultSet<PatchSet> patches;
if (id.isComplete()) {
patches = db.patchSets().byRevision(id);
} else {
patches = db.patchSets().byRevisionRange(id, id.max());
}
for (PatchSet p : patches) {
want.add(p.getId().getParentKey());
}
} else if (query.contains("owner:")) {
String[] parsedQuery = query.split(":");
if (parsedQuery.length > 1) {
filterBySortKey(result, changesCreatedBy(db, parsedQuery[1]), cmp, key);
}
} else if (query.contains("reviewer:")) {
String[] parsedQuery = query.split(":");
if (parsedQuery.length > 1) {
want.addAll(changesReviewedBy(db, parsedQuery[1]));
}
}
if (result.isEmpty() && want.isEmpty()) {
return new ListResultSet<Change>(Collections.<Change> emptyList());
}
filterBySortKey(result, db.changes().get(want), cmp, key);
Collections.sort(result, cmp);
if (limit < result.size()) {
result = result.subList(0, limit);
}
return new ListResultSet<Change>(result);
}
private static void filterBySortKey(final List<Change> dst,
final Iterable<Change> src, final Comparator<Change> cmp, final String key) {
if (cmp == QUERY_PREV) {
for (Change c : src) {
if (c.getSortKey().compareTo(key) > 0) {
dst.add(c);
}
}
} else /* cmp == QUERY_NEXT */{
for (Change c : src) {
if (c.getSortKey().compareTo(key) < 0) {
dst.add(c);
}
}
}
}
public void forAccount(final Account.Id id,
final AsyncCallback<AccountDashboardInfo> callback) {
final Account.Id me = getAccountId();
final Account.Id target = id != null ? id : me;
if (target == null) {
callback.onFailure(new NoSuchEntityException());
return;
}
run(callback, new Action<AccountDashboardInfo>() {
public AccountDashboardInfo run(final ReviewDb db) throws OrmException,
Failure {
final AccountInfoCacheFactory ac = accountInfoCacheFactory.create();
final Account user = ac.get(target);
if (user == null) {
throw new Failure(new NoSuchEntityException());
}
final Set<Change.Id> stars = currentUser.get().getStarredChanges();
final ChangeAccess changes = db.changes();
final AccountDashboardInfo d;
final Set<Change.Id> openReviews = new HashSet<Change.Id>();
final Set<Change.Id> closedReviews = new HashSet<Change.Id>();
for (final PatchSetApproval ca : db.patchSetApprovals().openByUser(id)) {
openReviews.add(ca.getPatchSetId().getParentKey());
}
for (final PatchSetApproval ca : db.patchSetApprovals()
.closedByUser(id)) {
closedReviews.add(ca.getPatchSetId().getParentKey());
}
d = new AccountDashboardInfo(target);
d.setByOwner(filter(changes.byOwnerOpen(target), stars, ac));
d.setClosed(filter(changes.byOwnerClosed(target), stars, ac));
for (final ChangeInfo c : d.getByOwner()) {
openReviews.remove(c.getId());
}
d.setForReview(filter(changes.get(openReviews), stars, ac));
Collections.sort(d.getForReview(), ID_COMP);
for (final ChangeInfo c : d.getClosed()) {
closedReviews.remove(c.getId());
}
if (!closedReviews.isEmpty()) {
d.getClosed().addAll(filter(changes.get(closedReviews), stars, ac));
Collections.sort(d.getClosed(), SORT_KEY_COMP);
}
d.setAccounts(ac.create());
return d;
}
});
}
public void myStarredChanges(
final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new Action<SingleListChangeInfo>() {
public SingleListChangeInfo run(final ReviewDb db) throws OrmException {
final Account.Id me = getAccountId();
final AccountInfoCacheFactory ac = accountInfoCacheFactory.create();
final SingleListChangeInfo d = new SingleListChangeInfo();
final Set<Change.Id> starred = currentUser.get().getStarredChanges();
d.setChanges(filter(db.changes().get(starred), starred, ac));
Collections.sort(d.getChanges(), new Comparator<ChangeInfo>() {
public int compare(final ChangeInfo o1, final ChangeInfo o2) {
return o1.getLastUpdatedOn().compareTo(o2.getLastUpdatedOn());
}
});
d.setAccounts(ac.create());
return d;
}
});
}
public void myDraftChanges(final AsyncCallback<SingleListChangeInfo> callback) {
run(callback, new Action<SingleListChangeInfo>() {
public SingleListChangeInfo run(final ReviewDb db) throws OrmException {
final Account.Id me = getAccountId();
final AccountInfoCacheFactory ac = accountInfoCacheFactory.create();
final SingleListChangeInfo d = new SingleListChangeInfo();
final Set<Change.Id> starred = currentUser.get().getStarredChanges();
final Set<Change.Id> drafted = draftedBy(db, me);
d.setChanges(filter(db.changes().get(drafted), starred, ac));
Collections.sort(d.getChanges(), new Comparator<ChangeInfo>() {
public int compare(final ChangeInfo o1, final ChangeInfo o2) {
return o1.getLastUpdatedOn().compareTo(o2.getLastUpdatedOn());
}
});
d.setAccounts(ac.create());
return d;
}
});
}
public void toggleStars(final ToggleStarRequest req,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException {
final Account.Id me = getAccountId();
final Set<Change.Id> existing = currentUser.get().getStarredChanges();
final ArrayList<StarredChange> add = new ArrayList<StarredChange>();
final ArrayList<StarredChange> remove = new ArrayList<StarredChange>();
if (req.getAddSet() != null) {
for (final Change.Id id : req.getAddSet()) {
if (!existing.contains(id)) {
add.add(new StarredChange(new StarredChange.Key(me, id)));
}
}
}
if (req.getRemoveSet() != null) {
for (final Change.Id id : req.getRemoveSet()) {
if (existing.contains(id)) {
remove.add(new StarredChange(new StarredChange.Key(me, id)));
}
}
}
if (!add.isEmpty() || !remove.isEmpty()) {
final Transaction txn = db.beginTransaction();
db.starredChanges().insert(add);
db.starredChanges().delete(remove);
txn.commit();
}
return VoidResult.INSTANCE;
}
});
}
public void myStarredChangeIds(final AsyncCallback<Set<Change.Id>> callback) {
callback.onSuccess(currentUser.get().getStarredChanges());
}
private List<ChangeInfo> filter(final ResultSet<Change> rs,
final Set<Change.Id> starred, final AccountInfoCacheFactory accts) {
final ArrayList<ChangeInfo> r = new ArrayList<ChangeInfo>();
for (final Change c : rs) {
if (canRead(c)) {
final ChangeInfo ci = new ChangeInfo(c);
accts.want(ci.getOwner());
ci.setStarred(starred.contains(ci.getId()));
r.add(ci);
}
}
return r;
}
private static Set<Change.Id> draftedBy(final ReviewDb db, final Account.Id me)
throws OrmException {
final Set<Change.Id> existing = new HashSet<Change.Id>();
if (me != null) {
for (final PatchLineComment sc : db.patchComments().draftByAuthor(me)) {
final Change.Id c =
sc.getKey().getParentKey().getParentKey().getParentKey();
existing.add(c);
}
}
return existing;
}
/**
* @return a set of all the account ID's matching the given user name in
* either of the following columns: ssh name, email address, full name
*/
private static Set<Account.Id> getAccountSources(final ReviewDb db,
final String userName) throws OrmException {
Set<Account.Id> result = new HashSet<Account.Id>();
String a = userName;
String b = userName + "\u9fa5";
addAll(result, db.accounts().suggestBySshUserName(a, b, 10));
addAll(result, db.accounts().suggestByFullName(a, b, 10));
for (AccountExternalId extId : db.accountExternalIds()
.suggestByEmailAddress(a, b, 10)) {
result.add(extId.getAccountId());
}
return result;
}
private static void addAll(Set<Account.Id> result, ResultSet<Account> rs) {
for (Account account : rs) {
result.add(account.getId());
}
}
/**
* @return a set of all the changes created by userName. This method tries to
* find userName in 1) the ssh user names, 2) the full names and 3)
* the email addresses. The returned changes are unique and sorted by
* time stamp, newer first.
*/
private List<Change> changesCreatedBy(final ReviewDb db, final String userName)
throws OrmException {
final List<Change> resultChanges = new ArrayList<Change>();
for (Account.Id account : getAccountSources(db, userName)) {
for (Change change : db.changes().byOwnerOpen(account)) {
resultChanges.add(change);
}
for (Change change : db.changes().byOwnerClosedAll(account)) {
resultChanges.add(change);
}
}
return resultChanges;
}
/**
* @return a set of all the changes reviewed by userName. This method tries to
* find userName in 1) the ssh user names, 2) the full names and the
* email addresses. The returned changes are unique and sorted by time
* stamp, newer first.
*/
private Set<Change.Id> changesReviewedBy(final ReviewDb db,
final String userName) throws OrmException {
final Set<Change.Id> resultChanges = new HashSet<Change.Id>();
for (Account.Id account : getAccountSources(db, userName)) {
for (PatchSetApproval a : db.patchSetApprovals().openByUser(account)) {
resultChanges.add(a.getPatchSetId().getParentKey());
}
for (PatchSetApproval a : db.patchSetApprovals().closedByUserAll(account)) {
resultChanges.add(a.getPatchSetId().getParentKey());
}
}
return resultChanges;
}
private abstract class QueryNext implements Action<SingleListChangeInfo> {
protected final String pos;
protected final int limit;
protected final int slim;
QueryNext(final int pageSize, final String pos) {
this.pos = pos;
this.limit = safePageSize(pageSize);
this.slim = limit + 1;
}
public SingleListChangeInfo run(final ReviewDb db) throws OrmException {
final Account.Id me = getAccountId();
final AccountInfoCacheFactory ac = accountInfoCacheFactory.create();
final SingleListChangeInfo d = new SingleListChangeInfo();
final Set<Change.Id> starred = currentUser.get().getStarredChanges();
boolean results = true;
String sortKey = pos;
final ArrayList<ChangeInfo> list = new ArrayList<ChangeInfo>();
while (results && list.size() < slim) {
results = false;
final ResultSet<Change> rs = query(db, slim, sortKey);
for (final Change c : rs) {
results = true;
if (canRead(c)) {
final ChangeInfo ci = new ChangeInfo(c);
ac.want(ci.getOwner());
ci.setStarred(starred.contains(ci.getId()));
list.add(ci);
if (list.size() == slim) {
rs.close();
break;
}
}
sortKey = c.getSortKey();
}
}
final boolean atEnd = finish(list);
d.setChanges(list, atEnd);
d.setAccounts(ac.create());
return d;
}
boolean finish(final ArrayList<ChangeInfo> list) {
final boolean atEnd = list.size() <= limit;
if (list.size() == slim) {
list.remove(limit);
}
return atEnd;
}
abstract ResultSet<Change> query(final ReviewDb db, final int slim,
String sortKey) throws OrmException;
}
private abstract class QueryPrev extends QueryNext {
QueryPrev(int pageSize, String pos) {
super(pageSize, pos);
}
@Override
boolean finish(final ArrayList<ChangeInfo> list) {
final boolean atEnd = super.finish(list);
Collections.reverse(list);
return atEnd;
}
}
}

View File

@@ -0,0 +1,124 @@
// 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.httpd.rpc;
import com.google.gerrit.common.auth.SignInRequired;
import com.google.gerrit.common.errors.NotSignedInException;
import com.google.gerrit.httpd.WebSession;
import com.google.gson.GsonBuilder;
import com.google.gwtjsonrpc.client.RemoteJsonService;
import com.google.gwtjsonrpc.server.ActiveCall;
import com.google.gwtjsonrpc.server.JsonServlet;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Base JSON servlet to ensure the current user is not forged.
*/
@SuppressWarnings("serial")
final class GerritJsonServlet extends JsonServlet<GerritJsonServlet.GerritCall> {
private final Provider<WebSession> session;
private final RemoteJsonService service;
@Inject
GerritJsonServlet(final Provider<WebSession> w, final RemoteJsonService s) {
session = w;
service = s;
}
@Override
protected GerritCall createActiveCall(final HttpServletRequest req,
final HttpServletResponse rsp) {
return new GerritCall(session.get(), req, rsp);
}
@Override
protected GsonBuilder createGsonBuilder() {
final GsonBuilder g = super.createGsonBuilder();
g.registerTypeAdapter(org.eclipse.jgit.diff.Edit.class,
new org.eclipse.jgit.diff.EditDeserializer());
return g;
}
@Override
protected void preInvoke(final GerritCall call) {
super.preInvoke(call);
if (call.isComplete()) {
return;
}
if (call.getMethod().getAnnotation(SignInRequired.class) != null) {
// If SignInRequired is set on this method we must have both a
// valid XSRF token *and* have the user signed in. Doing these
// checks also validates that they agree on the user identity.
//
if (!call.requireXsrfValid() || !session.get().isSignedIn()) {
call.onFailure(new NotSignedInException());
return;
}
}
}
@Override
protected Object createServiceHandle() {
return service;
}
static class GerritCall extends ActiveCall {
private final WebSession session;
GerritCall(final WebSession session, final HttpServletRequest i,
final HttpServletResponse o) {
super(i, o);
this.session = session;
}
@Override
public void onFailure(final Throwable error) {
if (error instanceof IllegalArgumentException
|| error instanceof IllegalStateException) {
super.onFailure(error);
} else if (error instanceof OrmException
|| error instanceof RuntimeException) {
onInternalFailure(error);
} else {
super.onFailure(error);
}
}
@Override
public boolean xsrfValidate() {
final String keyIn = getXsrfKeyIn();
if (keyIn == null || "".equals(keyIn)) {
// Anonymous requests don't need XSRF protection, they shouldn't
// be able to cause critical state changes.
//
return !session.isSignedIn();
} else {
// The session must exist, and must be using this token.
//
return session.isSignedIn() && session.isTokenValid(keyIn);
}
}
}
}

View File

@@ -0,0 +1,45 @@
// 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.rpc;
import com.google.gwtjsonrpc.client.RemoteJsonService;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
/** Creates {@link GerritJsonServlet} with a {@link RemoteJsonService}. */
class GerritJsonServletProvider implements Provider<GerritJsonServlet> {
@Inject
private Injector injector;
private final Class<? extends RemoteJsonService> serviceClass;
@Inject
GerritJsonServletProvider(final Class<? extends RemoteJsonService> c) {
serviceClass = c;
}
@Override
public GerritJsonServlet get() {
final RemoteJsonService srv = injector.getInstance(serviceClass);
return injector.createChildInjector(new AbstractModule() {
@Override
protected void configure() {
bind(RemoteJsonService.class).toInstance(srv);
}
}).getInstance(GerritJsonServlet.class);
}
}

View File

@@ -0,0 +1,94 @@
// 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.rpc;
import com.google.gerrit.common.errors.CorruptEntityException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
import java.util.concurrent.Callable;
/**
* Base class for RPC service implementations.
* <p>
* Typically an RPC service implementation will extend this class and use Guice
* injection to manage its state. For example:
*
* <pre>
* class Foo extends Handler&lt;Result&gt; {
* interface Factory {
* Foo create(... args ...);
* }
* &#064;Inject
* Foo(state, @Assisted args) { ... }
* Result get() throws Exception { ... }
* }
* </pre>
*
* @param <T> type of result for {@link AsyncCallback#onSuccess(Object)} if the
* operation completed successfully.
*/
public abstract class Handler<T> implements Callable<T> {
/**
* Run the operation and pass the result to the callback.
*
* @param callback callback to receive the result of {@link #call()}.
*/
public final void to(final AsyncCallback<T> callback) {
try {
final T r = call();
if (r != null) {
callback.onSuccess(r);
}
} catch (NoSuchProjectException e) {
callback.onFailure(new NoSuchEntityException());
} catch (NoSuchChangeException e) {
callback.onFailure(new NoSuchEntityException());
} catch (OrmException e) {
if (e.getCause() instanceof BaseServiceImplementation.Failure) {
callback.onFailure(e.getCause().getCause());
} else if (e.getCause() instanceof CorruptEntityException) {
callback.onFailure(e.getCause());
} else if (e.getCause() instanceof NoSuchEntityException) {
callback.onFailure(e.getCause());
} else {
callback.onFailure(e);
}
} catch (BaseServiceImplementation.Failure e) {
callback.onFailure(e.getCause());
} catch (Exception e) {
callback.onFailure(e);
}
}
/**
* Compute the operation result.
*
* @return the result of the operation. Return {@link VoidResult#INSTANCE} if
* there is no meaningful return value for the operation.
* @throws Exception the operation failed. The caller will log the exception
* and the stack trace, if it is worth logging on the server side.
*/
public abstract T call() throws Exception;
}

View File

@@ -0,0 +1,50 @@
// 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.rpc;
import com.google.gwtjsonrpc.client.RemoteJsonService;
import com.google.inject.Key;
import com.google.inject.Scopes;
import com.google.inject.internal.UniqueAnnotations;
import com.google.inject.servlet.ServletModule;
/** Binds {@link RemoteJsonService} implementations to a JSON servlet. */
public abstract class RpcServletModule extends ServletModule {
public static final String PREFIX = "/gerrit/rpc/";
private final String prefix;
protected RpcServletModule(final String pathPrefix) {
prefix = pathPrefix;
}
protected void rpc(Class<? extends RemoteJsonService> clazz) {
String name = clazz.getSimpleName();
if (name.endsWith("Impl")) {
name = name.substring(0, name.length() - 4);
}
rpc(name, clazz);
}
protected void rpc(final String name, Class<? extends RemoteJsonService> clazz) {
final Key<GerritJsonServlet> srv =
Key.get(GerritJsonServlet.class, UniqueAnnotations.create());
final GerritJsonServletProvider provider =
new GerritJsonServletProvider(clazz);
bind(clazz);
serve(prefix + name).with(srv);
bind(srv).toProvider(provider).in(Scopes.SINGLETON);
}
}

View File

@@ -0,0 +1,125 @@
// 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.httpd.rpc;
import com.google.gerrit.common.data.AccountInfo;
import com.google.gerrit.common.data.SuggestService;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
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 java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
class SuggestServiceImpl extends BaseServiceImplementation implements
SuggestService {
private static final String MAX_SUFFIX = "\u9fa5";
private final ProjectCache projectCache;
private final AccountCache accountCache;
private final Provider<CurrentUser> currentUser;
@Inject
SuggestServiceImpl(final Provider<ReviewDb> schema,
final ProjectCache projectCache, final AccountCache accountCache,
final Provider<CurrentUser> currentUser) {
super(schema, currentUser);
this.projectCache = projectCache;
this.accountCache = accountCache;
this.currentUser = currentUser;
}
public void suggestProjectNameKey(final String query, final int limit,
final AsyncCallback<List<Project.NameKey>> callback) {
run(callback, new Action<List<Project.NameKey>>() {
public List<Project.NameKey> run(final ReviewDb db) throws OrmException {
final String a = query;
final String b = a + MAX_SUFFIX;
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());
if (e != null && e.controlFor(user).isVisible()) {
r.add(p.getNameKey());
}
}
return r;
}
});
}
public void suggestAccount(final String query, final int limit,
final AsyncCallback<List<AccountInfo>> callback) {
run(callback, new Action<List<AccountInfo>>() {
public List<AccountInfo> run(final ReviewDb db) throws OrmException {
final String a = query;
final String b = a + MAX_SUFFIX;
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)) {
r.put(p.getId(), new AccountInfo(p));
}
if (r.size() < n) {
for (final Account p : db.accounts().suggestByPreferredEmail(a, b,
n - r.size())) {
r.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());
r.put(e.getAccountId(), info);
}
}
}
return new ArrayList<AccountInfo>(r.values());
}
});
}
public void suggestAccountGroup(final String query, final int limit,
final AsyncCallback<List<AccountGroup>> callback) {
run(callback, new Action<List<AccountGroup>>() {
public List<AccountGroup> run(final ReviewDb db) throws OrmException {
final String a = query;
final String b = a + MAX_SUFFIX;
final int max = 10;
final int n = limit <= 0 ? max : Math.min(limit, max);
return db.accountGroups().suggestByName(a, b, n).toList();
}
});
}
}

View File

@@ -0,0 +1,78 @@
// 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.httpd.rpc;
import com.google.gerrit.common.data.SshHostKey;
import com.google.gerrit.common.data.SystemInfoService;
import com.google.gerrit.reviewdb.ContributorAgreement;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.jcraft.jsch.HostKey;
import com.jcraft.jsch.JSch;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
class SystemInfoServiceImpl implements SystemInfoService {
private static final JSch JSCH = new JSch();
private final SchemaFactory<ReviewDb> schema;
private final List<HostKey> hostKeys;
private final Provider<HttpServletRequest> httpRequest;
@Inject
SystemInfoServiceImpl(final SchemaFactory<ReviewDb> sf, final SshInfo daemon,
final Provider<HttpServletRequest> hsr) {
schema = sf;
hostKeys = daemon.getHostKeys();
httpRequest = hsr;
}
public void contributorAgreements(
final AsyncCallback<List<ContributorAgreement>> callback) {
try {
final ReviewDb db = schema.open();
try {
callback.onSuccess(db.contributorAgreements().active().toList());
} finally {
db.close();
}
} catch (OrmException e) {
callback.onFailure(e);
}
}
public void daemonHostKeys(final AsyncCallback<List<SshHostKey>> callback) {
final ArrayList<SshHostKey> r = new ArrayList<SshHostKey>(hostKeys.size());
for (final HostKey hk : hostKeys) {
String host = hk.getHost();
if (host.startsWith("*:")) {
final String port = host.substring(2);
host = "[" + httpRequest.get().getServerName() + "]:" + port;
}
final String fp = hk.getFingerPrint(JSCH);
r.add(new SshHostKey(host, hk.getType() + " " + hk.getKey(), fp));
}
callback.onSuccess(r);
}
}

View File

@@ -0,0 +1,39 @@
// 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.rpc;
import com.google.gerrit.httpd.rpc.account.AccountModule;
import com.google.gerrit.httpd.rpc.changedetail.ChangeModule;
import com.google.gerrit.httpd.rpc.patch.PatchModule;
import com.google.gerrit.httpd.rpc.project.ProjectModule;
/** Registers servlets to answer RPCs from client UI. */
public class UiRpcModule extends RpcServletModule {
public UiRpcModule() {
super(PREFIX);
}
@Override
protected void configureServlets() {
rpc(ChangeListServiceImpl.class);
rpc(SuggestServiceImpl.class);
rpc(SystemInfoServiceImpl.class);
install(new AccountModule());
install(new ChangeModule());
install(new PatchModule());
install(new ProjectModule());
}
}

View File

@@ -0,0 +1,41 @@
// 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.rpc.account;
import com.google.gerrit.httpd.rpc.RpcServletModule;
import com.google.gerrit.httpd.rpc.UiRpcModule;
import com.google.gerrit.server.config.FactoryModule;
public class AccountModule extends RpcServletModule {
public AccountModule() {
super(UiRpcModule.PREFIX);
}
@Override
protected void configureServlets() {
install(new FactoryModule() {
@Override
protected void configure() {
factory(AgreementInfoFactory.Factory.class);
factory(ExternalIdDetailFactory.Factory.class);
factory(GroupDetailFactory.Factory.class);
factory(MyGroupsFactory.Factory.class);
}
});
rpc(AccountSecurityImpl.class);
rpc(AccountServiceImpl.class);
rpc(GroupAdminServiceImpl.class);
}
}

View File

@@ -0,0 +1,385 @@
// 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.httpd.rpc.account;
import com.google.gerrit.common.data.AccountSecurity;
import com.google.gerrit.common.errors.ContactInformationStoreException;
import com.google.gerrit.common.errors.InvalidSshKeyException;
import com.google.gerrit.common.errors.InvalidSshUserNameException;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountAgreement;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountSshKey;
import com.google.gerrit.reviewdb.ContactInformation;
import com.google.gerrit.reviewdb.ContributorAgreement;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountByEmailCache;
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.Realm;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.contact.ContactStore;
import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.mail.RegisterNewEmailSender;
import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtjsonrpc.server.ValidToken;
import com.google.gwtjsonrpc.server.XsrfException;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.jgit.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
class AccountSecurityImpl extends BaseServiceImplementation implements
AccountSecurity {
private static final Pattern SSH_USER_NAME_PATTERN = Pattern.compile(Account.SSH_USER_NAME_PATTERN);
private final Logger log = LoggerFactory.getLogger(getClass());
private final ContactStore contactStore;
private final AuthConfig authConfig;
private final Realm realm;
private final RegisterNewEmailSender.Factory registerNewEmailFactory;
private final SshKeyCache sshKeyCache;
private final AccountByEmailCache byEmailCache;
private final AccountCache accountCache;
private final AccountManager accountManager;
private final boolean useContactInfo;
private final ExternalIdDetailFactory.Factory externalIdDetailFactory;
private final MyGroupsFactory.Factory myGroupsFactory;
@Inject
AccountSecurityImpl(final Provider<ReviewDb> schema,
final Provider<CurrentUser> currentUser, final ContactStore cs,
final AuthConfig ac, final Realm r,
final RegisterNewEmailSender.Factory esf, final SshKeyCache skc,
final AccountByEmailCache abec, final AccountCache uac,
final AccountManager am,
final ExternalIdDetailFactory.Factory externalIdDetailFactory,
final MyGroupsFactory.Factory myGroupsFactory) {
super(schema, currentUser);
contactStore = cs;
authConfig = ac;
realm = r;
registerNewEmailFactory = esf;
sshKeyCache = skc;
byEmailCache = abec;
accountCache = uac;
accountManager = am;
useContactInfo = contactStore != null && contactStore.isEnabled();
this.externalIdDetailFactory = externalIdDetailFactory;
this.myGroupsFactory = myGroupsFactory;
}
public void mySshKeys(final AsyncCallback<List<AccountSshKey>> callback) {
run(callback, new Action<List<AccountSshKey>>() {
public List<AccountSshKey> run(ReviewDb db) throws OrmException {
return db.accountSshKeys().byAccount(getAccountId()).toList();
}
});
}
public void addSshKey(final String keyText,
final AsyncCallback<AccountSshKey> callback) {
run(callback, new Action<AccountSshKey>() {
public AccountSshKey run(final ReviewDb db) throws OrmException, Failure {
int max = 0;
final Account.Id me = getAccountId();
for (final AccountSshKey k : db.accountSshKeys().byAccount(me)) {
max = Math.max(max, k.getKey().get());
}
final AccountSshKey key;
try {
key = sshKeyCache.create(new AccountSshKey.Id(me, max + 1), keyText);
} catch (InvalidSshKeyException e) {
throw new Failure(e);
}
db.accountSshKeys().insert(Collections.singleton(key));
uncacheSshKeys(me);
return key;
}
});
}
public void deleteSshKeys(final Set<AccountSshKey.Id> ids,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final Account.Id me = getAccountId();
for (final AccountSshKey.Id keyId : ids) {
if (!me.equals(keyId.getParentKey()))
throw new Failure(new NoSuchEntityException());
}
final List<AccountSshKey> k = db.accountSshKeys().get(ids).toList();
if (!k.isEmpty()) {
final Transaction txn = db.beginTransaction();
db.accountSshKeys().delete(k, txn);
txn.commit();
uncacheSshKeys(me);
}
return VoidResult.INSTANCE;
}
});
}
private void uncacheSshKeys(final Account.Id me) {
uncacheSshKeys(accountCache.get(me).getAccount().getSshUserName());
}
private void uncacheSshKeys(final String userName) {
sshKeyCache.evict(userName);
}
@Override
public void changeSshUserName(final String newName,
final AsyncCallback<VoidResult> callback) {
if (!realm.allowsEdit(Account.FieldName.SSH_USER_NAME)) {
callback.onFailure(new NameAlreadyUsedException());
return;
}
run(callback, new Action<VoidResult>() {
@Override
public VoidResult run(ReviewDb db) throws OrmException, Failure {
final Account me = db.accounts().get(getAccountId());
if (me == null) {
throw new Failure(new NoSuchEntityException());
}
if (newName != null && !SSH_USER_NAME_PATTERN.matcher(newName).matches()) {
throw new Failure(new InvalidSshUserNameException());
}
final Account other;
if (newName != null) {
other = db.accounts().bySshUserName(newName);
} else {
other = null;
}
if (other != null) {
if (other.getId().equals(me.getId())) {
return VoidResult.INSTANCE;
} else {
throw new Failure(new NameAlreadyUsedException());
}
}
final String oldName = me.getSshUserName();
me.setSshUserName(newName);
db.accounts().update(Collections.singleton(me));
uncacheSshKeys(oldName);
uncacheSshKeys(newName);
accountCache.evict(me.getId());
return VoidResult.INSTANCE;
}
});
}
public void myExternalIds(AsyncCallback<List<AccountExternalId>> callback) {
externalIdDetailFactory.create().to(callback);
}
@Override
public void myGroups(final AsyncCallback<List<AccountGroup>> callback) {
myGroupsFactory.create().to(callback);
}
public void deleteExternalIds(final Set<AccountExternalId.Key> keys,
final AsyncCallback<Set<AccountExternalId.Key>> callback) {
run(callback, new Action<Set<AccountExternalId.Key>>() {
public Set<AccountExternalId.Key> run(final ReviewDb db)
throws OrmException, Failure {
// Determine the records we will allow the user to remove.
//
final Account.Id me = getAccountId();
final Map<AccountExternalId.Key, AccountExternalId> all =
db.accountExternalIds()
.toMap(db.accountExternalIds().byAccount(me));
// Don't permit deletes unless they are for our own account
//
for (final AccountExternalId.Key keyId : keys) {
if (!all.containsKey(keyId))
throw new Failure(new NoSuchEntityException());
}
final AccountExternalId mostRecent =
AccountExternalId.mostRecent(all.values());
final Set<AccountExternalId.Key> removed =
new HashSet<AccountExternalId.Key>();
final List<AccountExternalId> toDelete =
new ArrayList<AccountExternalId>();
for (final AccountExternalId.Key k : keys) {
final AccountExternalId e = all.get(k);
if (e == null) {
// Its already gone, tell the client its gone
//
removed.add(k);
} else if (e == mostRecent) {
// Don't delete the most recently accessed identity; the
// user might lock themselves out of the account.
//
continue;
} else {
toDelete.add(e);
removed.add(e.getKey());
}
}
if (!toDelete.isEmpty()) {
final Transaction txn = db.beginTransaction();
db.accountExternalIds().delete(toDelete, txn);
txn.commit();
accountCache.evict(me);
for (AccountExternalId e : toDelete) {
byEmailCache.evict(e.getEmailAddress());
}
}
return removed;
}
});
}
public void updateContact(final String name, final String emailAddr,
final ContactInformation info, final AsyncCallback<Account> callback) {
run(callback, new Action<Account>() {
public Account run(ReviewDb db) throws OrmException, Failure {
final Account me = db.accounts().get(getAccountId());
final String oldEmail = me.getPreferredEmail();
if (realm.allowsEdit(Account.FieldName.FULL_NAME)) {
me.setFullName(name != null && !name.isEmpty() ? name : null);
}
me.setPreferredEmail(emailAddr);
if (useContactInfo) {
if (ContactInformation.hasAddress(info)
|| (me.isContactFiled() && ContactInformation.hasData(info))) {
me.setContactFiled();
}
if (ContactInformation.hasData(info)) {
try {
contactStore.store(me, info);
} catch (ContactInformationStoreException e) {
throw new Failure(e);
}
}
}
db.accounts().update(Collections.singleton(me));
if (!eq(oldEmail, me.getPreferredEmail())) {
byEmailCache.evict(oldEmail);
byEmailCache.evict(me.getPreferredEmail());
}
accountCache.evict(me.getId());
return me;
}
});
}
private static boolean eq(final String a, final String b) {
if (a == null && b == null) {
return true;
}
return a != null && a.equals(b);
}
public void enterAgreement(final ContributorAgreement.Id id,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final ContributorAgreement cla = db.contributorAgreements().get(id);
if (cla == null || !cla.isActive()) {
throw new Failure(new NoSuchEntityException());
}
final AccountAgreement a =
new AccountAgreement(new AccountAgreement.Key(getAccountId(), id));
if (cla.isAutoVerify()) {
a.review(AccountAgreement.Status.VERIFIED, null);
}
db.accountAgreements().insert(Collections.singleton(a));
return VoidResult.INSTANCE;
}
});
}
public void registerEmail(final String address,
final AsyncCallback<VoidResult> cb) {
try {
final RegisterNewEmailSender sender;
sender = registerNewEmailFactory.create(address);
sender.send();
cb.onSuccess(VoidResult.INSTANCE);
} catch (EmailException e) {
log.error("Cannot send email verification message to " + address, e);
cb.onFailure(e);
} catch (RuntimeException e) {
log.error("Cannot send email verification message to " + address, e);
cb.onFailure(e);
}
}
public void validateEmail(final String token,
final AsyncCallback<VoidResult> callback) {
try {
final ValidToken t =
authConfig.getEmailRegistrationToken().checkToken(token, null);
if (t == null || t.getData() == null || "".equals(t.getData())) {
callback.onFailure(new IllegalStateException("Invalid token"));
return;
}
final String newEmail = new String(Base64.decode(t.getData()), "UTF-8");
if (!newEmail.contains("@")) {
callback.onFailure(new IllegalStateException("Invalid token"));
return;
}
accountManager.link(getAccountId(), AuthRequest.forEmail(newEmail));
callback.onSuccess(VoidResult.INSTANCE);
} catch (XsrfException e) {
callback.onFailure(e);
} catch (UnsupportedEncodingException e) {
callback.onFailure(e);
} catch (AccountException e) {
callback.onFailure(e);
}
}
}

View File

@@ -0,0 +1,172 @@
// 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.httpd.rpc.account;
import com.google.gerrit.common.data.AccountProjectWatchInfo;
import com.google.gerrit.common.data.AccountService;
import com.google.gerrit.common.data.AgreementInfo;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.AccountProjectWatch;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
class AccountServiceImpl extends BaseServiceImplementation implements
AccountService {
private final Provider<IdentifiedUser> currentUser;
private final AccountCache accountCache;
private final ProjectControl.Factory projectControlFactory;
private final AgreementInfoFactory.Factory agreementInfoFactory;
@Inject
AccountServiceImpl(final Provider<ReviewDb> schema,
final Provider<IdentifiedUser> identifiedUser,
final AccountCache accountCache,
final ProjectControl.Factory projectControlFactory,
final AgreementInfoFactory.Factory agreementInfoFactory) {
super(schema, identifiedUser);
this.currentUser = identifiedUser;
this.accountCache = accountCache;
this.projectControlFactory = projectControlFactory;
this.agreementInfoFactory = agreementInfoFactory;
}
public void myAccount(final AsyncCallback<Account> callback) {
callback.onSuccess(currentUser.get().getAccount());
}
public void changePreferences(final AccountGeneralPreferences pref,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final Account a = db.accounts().get(getAccountId());
if (a == null) {
throw new Failure(new NoSuchEntityException());
}
a.setGeneralPreferences(pref);
db.accounts().update(Collections.singleton(a));
accountCache.evict(a.getId());
return VoidResult.INSTANCE;
}
});
}
public void myProjectWatch(
final AsyncCallback<List<AccountProjectWatchInfo>> callback) {
run(callback, new Action<List<AccountProjectWatchInfo>>() {
public List<AccountProjectWatchInfo> run(ReviewDb db) throws OrmException {
final List<AccountProjectWatchInfo> r =
new ArrayList<AccountProjectWatchInfo>();
for (final AccountProjectWatch w : db.accountProjectWatches()
.byAccount(getAccountId()).toList()) {
final ProjectControl ctl;
try {
ctl = projectControlFactory.validateFor(w.getProjectNameKey());
} catch (NoSuchProjectException e) {
db.accountProjectWatches().delete(Collections.singleton(w));
continue;
}
r.add(new AccountProjectWatchInfo(w, ctl.getProject()));
}
Collections.sort(r, new Comparator<AccountProjectWatchInfo>() {
public int compare(final AccountProjectWatchInfo a,
final AccountProjectWatchInfo b) {
return a.getProject().getName().compareTo(b.getProject().getName());
}
});
return r;
}
});
}
public void addProjectWatch(final String projectName,
final AsyncCallback<AccountProjectWatchInfo> callback) {
run(callback, new Action<AccountProjectWatchInfo>() {
public AccountProjectWatchInfo run(ReviewDb db) throws OrmException,
NoSuchProjectException {
final Project.NameKey nameKey = new Project.NameKey(projectName);
final ProjectControl ctl = projectControlFactory.validateFor(nameKey);
final AccountProjectWatch watch =
new AccountProjectWatch(
new AccountProjectWatch.Key(((IdentifiedUser) ctl
.getCurrentUser()).getAccountId(), nameKey));
db.accountProjectWatches().insert(Collections.singleton(watch));
return new AccountProjectWatchInfo(watch, ctl.getProject());
}
});
}
public void updateProjectWatch(final AccountProjectWatch watch,
final AsyncCallback<VoidResult> callback) {
if (!getAccountId().equals(watch.getAccountId())) {
callback.onFailure(new NoSuchEntityException());
return;
}
run(callback, new Action<VoidResult>() {
public VoidResult run(ReviewDb db) throws OrmException {
db.accountProjectWatches().update(Collections.singleton(watch));
return VoidResult.INSTANCE;
}
});
}
public void deleteProjectWatches(final Set<AccountProjectWatch.Key> keys,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final Account.Id me = getAccountId();
for (final AccountProjectWatch.Key keyId : keys) {
if (!me.equals(keyId.getParentKey()))
throw new Failure(new NoSuchEntityException());
}
final List<AccountProjectWatch> k =
db.accountProjectWatches().get(keys).toList();
if (!k.isEmpty()) {
final Transaction txn = db.beginTransaction();
db.accountProjectWatches().delete(k, txn);
txn.commit();
}
return VoidResult.INSTANCE;
}
});
}
public void myAgreements(final AsyncCallback<AgreementInfo> callback) {
agreementInfoFactory.create().to(callback);
}
}

View File

@@ -0,0 +1,80 @@
// 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.rpc.account;
import com.google.gerrit.common.data.AgreementInfo;
import com.google.gerrit.httpd.rpc.Handler;
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.inject.Inject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class AgreementInfoFactory extends Handler<AgreementInfo> {
interface Factory {
AgreementInfoFactory create();
}
private final ReviewDb db;
private final IdentifiedUser user;
private AgreementInfo info;
@Inject
AgreementInfoFactory(final ReviewDb db, final IdentifiedUser user) {
this.db = db;
this.user = user;
}
@Override
public AgreementInfo call() throws Exception {
final List<AccountAgreement> userAccepted =
db.accountAgreements().byAccount(user.getAccountId()).toList();
final List<AccountGroupAgreement> groupAccepted =
new ArrayList<AccountGroupAgreement>();
for (final AccountGroup.Id groupId : user.getEffectiveGroups()) {
groupAccepted.addAll(db.accountGroupAgreements().byGroup(groupId)
.toList());
}
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));
}
}
info = new AgreementInfo();
info.setUserAccepted(userAccepted);
info.setGroupAccepted(groupAccepted);
info.setAgreements(agreements);
return info;
}
}

View File

@@ -0,0 +1,53 @@
// 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.rpc.account;
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.inject.Inject;
import java.util.Collections;
import java.util.List;
class ExternalIdDetailFactory extends Handler<List<AccountExternalId>> {
interface Factory {
ExternalIdDetailFactory create();
}
private final ReviewDb db;
private final IdentifiedUser user;
private final AuthConfig authConfig;
@Inject
ExternalIdDetailFactory(final ReviewDb db, final IdentifiedUser user,
final AuthConfig authConfig) {
this.db = db;
this.user = user;
this.authConfig = authConfig;
}
@Override
public List<AccountExternalId> call() throws Exception {
final List<AccountExternalId> ids =
db.accountExternalIds().byAccount(user.getAccountId()).toList();
for (final AccountExternalId e : ids) {
e.setTrusted(authConfig.isIdentityTrustable(Collections.singleton(e)));
}
return ids;
}
}

View File

@@ -0,0 +1,363 @@
// 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.httpd.rpc.account;
import com.google.gerrit.common.data.GroupAdminService;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountGroupMember;
import com.google.gerrit.reviewdb.AccountGroupMemberAudit;
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.AccountResolver;
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.account.Realm;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
class GroupAdminServiceImpl extends BaseServiceImplementation implements
GroupAdminService {
private final Provider<IdentifiedUser> identifiedUser;
private final AccountCache accountCache;
private final AccountResolver accountResolver;
private final Realm accountRealm;
private final GroupCache groupCache;
private final GroupControl.Factory groupControlFactory;
private final GroupDetailFactory.Factory groupDetailFactory;
@Inject
GroupAdminServiceImpl(final Provider<ReviewDb> schema,
final Provider<IdentifiedUser> currentUser,
final AccountCache accountCache, final AccountResolver accountResolver,
final Realm accountRealm, final GroupCache groupCache,
final GroupControl.Factory groupControlFactory,
final GroupDetailFactory.Factory groupDetailFactory) {
super(schema, currentUser);
this.identifiedUser = currentUser;
this.accountCache = accountCache;
this.accountResolver = accountResolver;
this.accountRealm = accountRealm;
this.groupCache = groupCache;
this.groupControlFactory = groupControlFactory;
this.groupDetailFactory = groupDetailFactory;
}
public void ownedGroups(final AsyncCallback<List<AccountGroup>> callback) {
run(callback, new Action<List<AccountGroup>>() {
public List<AccountGroup> run(ReviewDb db) throws OrmException {
final IdentifiedUser user = identifiedUser.get();
final List<AccountGroup> result;
if (user.isAdministrator()) {
result = db.accountGroups().all().toList();
} else {
final HashSet<AccountGroup.Id> seen = new HashSet<AccountGroup.Id>();
result = new ArrayList<AccountGroup>();
for (final AccountGroup.Id myGroup : user.getEffectiveGroups()) {
for (AccountGroup group : db.accountGroups().ownedByGroup(myGroup)) {
final AccountGroup.Id id = group.getId();
if (!seen.add(id)) {
continue;
}
try {
GroupControl c = groupControlFactory.controlFor(id);
if (c.isOwner()) {
result.add(c.getAccountGroup());
}
} catch (NoSuchGroupException e) {
continue;
}
}
}
}
Collections.sort(result, new Comparator<AccountGroup>() {
public int compare(final AccountGroup a, final AccountGroup b) {
return a.getName().compareTo(b.getName());
}
});
return result;
}
});
}
public void createGroup(final String newName,
final AsyncCallback<AccountGroup.Id> callback) {
run(callback, new Action<AccountGroup.Id>() {
public AccountGroup.Id run(final ReviewDb db) throws OrmException,
Failure {
final AccountGroup.NameKey nameKey = new AccountGroup.NameKey(newName);
if (db.accountGroups().get(nameKey) != null) {
throw new Failure(new NameAlreadyUsedException());
}
final AccountGroup group =
new AccountGroup(nameKey, new AccountGroup.Id(db
.nextAccountGroupId()));
group.setNameKey(nameKey);
group.setType(AccountGroup.Type.INTERNAL);
group.setDescription("");
final Account.Id me = getAccountId();
final AccountGroupMember m =
new AccountGroupMember(
new AccountGroupMember.Key(me, group.getId()));
final Transaction txn = db.beginTransaction();
db.accountGroups().insert(Collections.singleton(group), txn);
db.accountGroupMembers().insert(Collections.singleton(m), txn);
db.accountGroupMembersAudit().insert(
Collections.singleton(new AccountGroupMemberAudit(m, me)), txn);
txn.commit();
accountCache.evict(m.getAccountId());
return group.getId();
}
});
}
public void groupDetail(final AccountGroup.Id groupId,
final AsyncCallback<GroupDetail> callback) {
groupDetailFactory.create(groupId).to(callback);
}
public void changeGroupDescription(final AccountGroup.Id groupId,
final String description, final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final AccountGroup group = db.accountGroups().get(groupId);
assertAmGroupOwner(db, group);
group.setDescription(description);
db.accountGroups().update(Collections.singleton(group));
groupCache.evict(group);
return VoidResult.INSTANCE;
}
});
}
public void changeGroupOwner(final AccountGroup.Id groupId,
final String newOwnerName, final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final AccountGroup group = db.accountGroups().get(groupId);
assertAmGroupOwner(db, group);
final AccountGroup owner =
db.accountGroups().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);
return VoidResult.INSTANCE;
}
});
}
public void renameGroup(final AccountGroup.Id groupId, final String newName,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final AccountGroup group = db.accountGroups().get(groupId);
assertAmGroupOwner(db, group);
final AccountGroup.NameKey oldKey = group.getNameKey();
final AccountGroup.NameKey newKey = new AccountGroup.NameKey(newName);
if (!newKey.equals(oldKey)) {
if (db.accountGroups().get(newKey) != null) {
throw new Failure(new NameAlreadyUsedException());
}
group.setNameKey(newKey);
db.accountGroups().update(Collections.singleton(group));
groupCache.evict(group);
groupCache.evictAfterRename(oldKey);
}
return VoidResult.INSTANCE;
}
});
}
public void changeGroupType(final AccountGroup.Id groupId,
final AccountGroup.Type newType, final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final AccountGroup group = db.accountGroups().get(groupId);
assertAmGroupOwner(db, group);
group.setType(newType);
db.accountGroups().update(Collections.singleton(group));
groupCache.evict(group);
return VoidResult.INSTANCE;
}
});
}
public void changeExternalGroup(final AccountGroup.Id groupId,
final AccountGroup.ExternalNameKey bindTo,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
final AccountGroup group = db.accountGroups().get(groupId);
assertAmGroupOwner(db, group);
group.setExternalNameKey(bindTo);
db.accountGroups().update(Collections.singleton(group));
groupCache.evict(group);
return VoidResult.INSTANCE;
}
});
}
public void searchExternalGroups(final String searchFilter,
final AsyncCallback<List<AccountGroup.ExternalNameKey>> callback) {
final ArrayList<AccountGroup.ExternalNameKey> matches =
new ArrayList<AccountGroup.ExternalNameKey>(accountRealm
.lookupGroups(searchFilter));
Collections.sort(matches, new Comparator<AccountGroup.ExternalNameKey>() {
@Override
public int compare(AccountGroup.ExternalNameKey a,
AccountGroup.ExternalNameKey b) {
return a.get().compareTo(b.get());
}
});
callback.onSuccess(matches);
}
public void addGroupMember(final AccountGroup.Id groupId,
final String nameOrEmail, final AsyncCallback<GroupDetail> callback) {
run(callback, new Action<GroupDetail>() {
public GroupDetail run(ReviewDb db) throws OrmException, Failure,
NoSuchGroupException {
final GroupControl control = groupControlFactory.validateFor(groupId);
if (control.getAccountGroup().getType() != AccountGroup.Type.INTERNAL) {
throw new Failure(new NameAlreadyUsedException());
}
final Account a = findAccount(nameOrEmail);
if (!control.canAdd(a.getId())) {
throw new Failure(new NoSuchEntityException());
}
final AccountGroupMember.Key key =
new AccountGroupMember.Key(a.getId(), groupId);
AccountGroupMember m = db.accountGroupMembers().get(key);
if (m == null) {
m = new AccountGroupMember(key);
final Transaction txn = db.beginTransaction();
db.accountGroupMembers().insert(Collections.singleton(m), txn);
db.accountGroupMembersAudit().insert(
Collections.singleton(new AccountGroupMemberAudit(m,
getAccountId())), txn);
txn.commit();
accountCache.evict(m.getAccountId());
}
return groupDetailFactory.create(groupId).call();
}
});
}
public void deleteGroupMembers(final AccountGroup.Id groupId,
final Set<AccountGroupMember.Key> keys,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(final ReviewDb db) throws OrmException,
NoSuchGroupException, Failure {
final GroupControl control = groupControlFactory.validateFor(groupId);
if (control.getAccountGroup().getType() != AccountGroup.Type.INTERNAL) {
throw new Failure(new NameAlreadyUsedException());
}
for (final AccountGroupMember.Key k : keys) {
if (!groupId.equals(k.getAccountGroupId())) {
throw new Failure(new NoSuchEntityException());
}
}
final Account.Id me = getAccountId();
for (final AccountGroupMember.Key k : keys) {
final AccountGroupMember m = db.accountGroupMembers().get(k);
if (m != null) {
if (!control.canRemove(m.getAccountId())) {
throw new Failure(new NoSuchEntityException());
}
AccountGroupMemberAudit audit = null;
for (AccountGroupMemberAudit a : db.accountGroupMembersAudit()
.byGroupAccount(m.getAccountGroupId(), m.getAccountId())) {
if (a.isActive()) {
audit = a;
break;
}
}
final Transaction txn = db.beginTransaction();
db.accountGroupMembers().delete(Collections.singleton(m), txn);
if (audit != null) {
audit.removed(me);
db.accountGroupMembersAudit().update(
Collections.singleton(audit), txn);
} else {
audit = new AccountGroupMemberAudit(m, me);
audit.removedLegacy();
db.accountGroupMembersAudit().insert(
Collections.singleton(audit), txn);
}
txn.commit();
accountCache.evict(m.getAccountId());
}
}
return VoidResult.INSTANCE;
}
});
}
private void assertAmGroupOwner(final ReviewDb db, final AccountGroup group)
throws Failure {
try {
if (!groupControlFactory.controlFor(group.getId()).isOwner()) {
throw new Failure(new NoSuchGroupException(group.getId()));
}
} catch (NoSuchGroupException e) {
throw new Failure(new NoSuchGroupException(group.getId()));
}
}
private Account findAccount(final String nameOrEmail) throws OrmException,
Failure {
final Account r = accountResolver.find(nameOrEmail);
if (r == null) {
throw new Failure(new NoSuchAccountException(nameOrEmail));
}
return r;
}
}

View File

@@ -0,0 +1,111 @@
// 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.rpc.account;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountGroupMember;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.NoSuchGroupException;
import com.google.gwtorm.client.OrmException;
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.List;
class GroupDetailFactory extends Handler<GroupDetail> {
interface Factory {
GroupDetailFactory create(AccountGroup.Id groupId);
}
private final ReviewDb db;
private final GroupControl.Factory groupControl;
private final GroupCache groupCache;
private final AccountInfoCacheFactory aic;
private final AccountGroup.Id groupId;
private GroupControl control;
@Inject
GroupDetailFactory(final ReviewDb db,
final GroupControl.Factory groupControl, final GroupCache groupCache,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
@Assisted final AccountGroup.Id groupId) {
this.db = db;
this.groupControl = groupControl;
this.groupCache = groupCache;
this.aic = accountInfoCacheFactory.create();
this.groupId = groupId;
}
@Override
public GroupDetail call() throws OrmException, NoSuchGroupException {
control = groupControl.validateFor(groupId);
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.setAccounts(aic.create());
return detail;
}
private List<AccountGroupMember> loadMembers() throws OrmException {
List<AccountGroupMember> members = new ArrayList<AccountGroupMember>();
for (final AccountGroupMember m : db.accountGroupMembers().byGroup(groupId)) {
if (control.canSee(m.getAccountId())) {
aic.want(m.getAccountId());
members.add(m);
}
}
Collections.sort(members, new Comparator<AccountGroupMember>() {
public int compare(final AccountGroupMember o1,
final AccountGroupMember o2) {
final Account a = aic.get(o1.getAccountId());
final Account b = aic.get(o2.getAccountId());
return n(a).compareTo(n(b));
}
private String n(final Account a) {
String n = a.getFullName();
if (n != null && n.length() > 0) {
return n;
}
n = a.getPreferredEmail();
if (n != null && n.length() > 0) {
return n;
}
return a.getId().toString();
}
});
return members;
}
}

View File

@@ -0,0 +1,59 @@
// 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.rpc.account;
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.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 {
MyGroupsFactory create();
}
private final GroupCache groupCache;
private final IdentifiedUser user;
@Inject
MyGroupsFactory(final GroupCache groupCache, final IdentifiedUser user) {
this.groupCache = groupCache;
this.user = user;
}
@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));
}
Collections.sort(groupList, new Comparator<AccountGroup>() {
@Override
public int compare(AccountGroup a, AccountGroup b) {
return a.getName().compareTo(b.getName());
}
});
return groupList;
}
}

View File

@@ -0,0 +1,143 @@
// 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.rpc.changedetail;
import com.google.gerrit.common.data.ChangeDetail;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.Nullable;
import com.google.gerrit.server.mail.AbandonedSender;
import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.OrmRunnable;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Collections;
import java.util.List;
class AbandonChange extends Handler<ChangeDetail> {
interface Factory {
AbandonChange create(PatchSet.Id patchSetId, String message);
}
private final ChangeControl.Factory changeControlFactory;
private final ReviewDb db;
private final IdentifiedUser currentUser;
private final AbandonedSender.Factory abandonedSenderFactory;
private final ChangeDetailFactory.Factory changeDetailFactory;
private final PatchSet.Id patchSetId;
@Nullable
private final String message;
@Inject
AbandonChange(final ChangeControl.Factory changeControlFactory,
final ReviewDb db, final IdentifiedUser currentUser,
final AbandonedSender.Factory abandonedSenderFactory,
final ChangeDetailFactory.Factory changeDetailFactory,
@Assisted final PatchSet.Id patchSetId,
@Assisted @Nullable final String message) {
this.changeControlFactory = changeControlFactory;
this.db = db;
this.currentUser = currentUser;
this.abandonedSenderFactory = abandonedSenderFactory;
this.changeDetailFactory = changeDetailFactory;
this.patchSetId = patchSetId;
this.message = message;
}
@Override
public ChangeDetail call() throws NoSuchChangeException, OrmException,
EmailException, NoSuchEntityException, PatchSetInfoNotAvailableException {
final Change.Id changeId = patchSetId.getParentKey();
final ChangeControl control = changeControlFactory.validateFor(changeId);
if (!control.canAbandon()) {
throw new NoSuchChangeException(changeId);
}
final Change change = control.getChange();
final PatchSet patch = db.patchSets().get(patchSetId);
if (patch == null) {
throw new NoSuchChangeException(changeId);
}
final ChangeMessage cmsg =
new ChangeMessage(new ChangeMessage.Key(changeId, ChangeUtil
.messageUUID(db)), currentUser.getAccountId());
final StringBuilder msgBuf =
new StringBuilder("Patch Set " + change.currentPatchSetId().get()
+ ": Abandoned");
if (message != null && message.length() > 0) {
msgBuf.append("\n\n");
msgBuf.append(message);
}
cmsg.setMessage(msgBuf.toString());
Boolean dbSuccess = db.run(new OrmRunnable<Boolean, ReviewDb>() {
public Boolean run(ReviewDb db, Transaction txn, boolean retry)
throws OrmException {
return doAbandonChange(message, change, patchSetId, cmsg, db, txn);
}
});
if (dbSuccess) {
// Email the reviewers
final AbandonedSender cm = abandonedSenderFactory.create(change);
cm.setFrom(currentUser.getAccountId());
cm.setReviewDb(db);
cm.setChangeMessage(cmsg);
cm.send();
}
return changeDetailFactory.create(changeId).call();
}
private Boolean doAbandonChange(final String message, final Change change,
final PatchSet.Id psid, final ChangeMessage cm, final ReviewDb db,
final Transaction txn) throws OrmException {
// Check to make sure the change status and current patchset ID haven't
// changed while the user was typing an abandon message
if (change.getStatus().isOpen() && change.currentPatchSetId().equals(psid)) {
change.setStatus(Change.Status.ABANDONED);
ChangeUtil.updated(change);
final List<PatchSetApproval> approvals =
db.patchSetApprovals().byChange(change.getId()).toList();
for (PatchSetApproval a : approvals) {
a.cache(change);
}
db.patchSetApprovals().update(approvals, txn);
db.changeMessages().insert(Collections.singleton(cm), txn);
db.changes().update(Collections.singleton(change), txn);
return Boolean.TRUE;
}
return Boolean.FALSE;
}
}

View File

@@ -0,0 +1,240 @@
// 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.httpd.rpc.changedetail;
import com.google.gerrit.common.data.ApprovalDetail;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.ChangeDetail;
import com.google.gerrit.common.data.ChangeInfo;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetAncestor;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RevId;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
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.workflow.CategoryFunction;
import com.google.gerrit.server.workflow.FunctionState;
import com.google.gwtorm.client.OrmException;
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.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** Creates a {@link ChangeDetail} from a {@link Change}. */
public class ChangeDetailFactory extends Handler<ChangeDetail> {
public interface Factory {
ChangeDetailFactory create(Change.Id id);
}
private final ApprovalTypes approvalTypes;
private final ChangeControl.Factory changeControlFactory;
private final FunctionState.Factory functionState;
private final PatchSetDetailFactory.Factory patchSetDetail;
private final AccountInfoCacheFactory aic;
private final ReviewDb db;
private final Change.Id changeId;
private ChangeDetail detail;
private ChangeControl control;
@Inject
ChangeDetailFactory(final ApprovalTypes approvalTypes,
final FunctionState.Factory functionState,
final PatchSetDetailFactory.Factory patchSetDetail, final ReviewDb db,
final ChangeControl.Factory changeControlFactory,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
@Assisted final Change.Id id) {
this.approvalTypes = approvalTypes;
this.functionState = functionState;
this.patchSetDetail = patchSetDetail;
this.db = db;
this.changeControlFactory = changeControlFactory;
this.aic = accountInfoCacheFactory.create();
this.changeId = id;
}
@Override
public ChangeDetail call() throws OrmException, NoSuchEntityException,
PatchSetInfoNotAvailableException, NoSuchChangeException {
control = changeControlFactory.validateFor(changeId);
final Change change = control.getChange();
final Project proj = control.getProject();
final PatchSet patch = db.patchSets().get(change.currentPatchSetId());
if (patch == null) {
throw new NoSuchEntityException();
}
aic.want(change.getOwner());
detail = new ChangeDetail();
detail.setChange(change);
detail.setAllowsAnonymous(control.forAnonymousUser().isVisible());
detail.setCanAbandon(change.getStatus().isOpen() && control.canAbandon());
detail.setStarred(control.getCurrentUser().getStarredChanges().contains(
changeId));
loadPatchSets();
loadMessages();
if (change.currentPatchSetId() != null) {
loadCurrentPatchSet();
}
load();
detail.setAccounts(aic.create());
return detail;
}
private void loadPatchSets() throws OrmException {
detail.setPatchSets(db.patchSets().byChange(changeId).toList());
}
private void loadMessages() throws OrmException {
detail.setMessages(db.changeMessages().byChange(changeId).toList());
for (final ChangeMessage m : detail.getMessages()) {
aic.want(m.getAuthor());
}
}
private void load() throws OrmException {
final PatchSet.Id psId = detail.getChange().currentPatchSetId();
final List<PatchSetApproval> allApprovals =
db.patchSetApprovals().byChange(changeId).toList();
if (detail.getChange().getStatus().isOpen()) {
final FunctionState fs =
functionState.create(detail.getChange(), psId, allApprovals);
final Set<ApprovalCategory.Id> missingApprovals =
new HashSet<ApprovalCategory.Id>();
final Set<ApprovalCategory.Id> currentActions =
new HashSet<ApprovalCategory.Id>();
for (final ApprovalType at : approvalTypes.getApprovalTypes()) {
CategoryFunction.forCategory(at.getCategory()).run(at, fs);
if (!fs.isValid(at)) {
missingApprovals.add(at.getCategory().getId());
}
}
for (final ApprovalType at : approvalTypes.getActionTypes()) {
if (CategoryFunction.forCategory(at.getCategory()).isValid(
control.getCurrentUser(), at, fs)) {
currentActions.add(at.getCategory().getId());
}
}
detail.setMissingApprovals(missingApprovals);
detail.setCurrentActions(currentActions);
}
final HashMap<Account.Id, ApprovalDetail> ad =
new HashMap<Account.Id, ApprovalDetail>();
for (PatchSetApproval ca : allApprovals) {
ApprovalDetail d = ad.get(ca.getAccountId());
if (d == null) {
d = new ApprovalDetail(ca.getAccountId());
ad.put(d.getAccount(), d);
}
if (ca.getPatchSetId().equals(psId)) {
d.add(ca);
}
}
final Account.Id owner = detail.getChange().getOwner();
if (ad.containsKey(owner)) {
// Ensure the owner always sorts to the top of the table
//
ad.get(owner).sortFirst();
}
aic.want(ad.keySet());
detail.setApprovals(ad.values());
}
private void loadCurrentPatchSet() throws OrmException,
NoSuchEntityException, PatchSetInfoNotAvailableException,
NoSuchChangeException {
final PatchSet.Id psId = detail.getChange().currentPatchSetId();
final PatchSetDetailFactory loader = patchSetDetail.create(psId);
loader.patchSet = detail.getCurrentPatchSet();
loader.control = control;
detail.setCurrentPatchSetDetail(loader.call());
final HashSet<Change.Id> changesToGet = new HashSet<Change.Id>();
final List<Change.Id> ancestorOrder = new ArrayList<Change.Id>();
for (PatchSetAncestor a : db.patchSetAncestors().ancestorsOf(psId)) {
for (PatchSet p : db.patchSets().byRevision(a.getAncestorRevision())) {
final Change.Id ck = p.getId().getParentKey();
if (changesToGet.add(ck)) {
ancestorOrder.add(ck);
}
}
}
final RevId cprev = loader.patchSet.getRevision();
final List<PatchSetAncestor> descendants =
cprev != null ? db.patchSetAncestors().descendantsOf(cprev).toList()
: Collections.<PatchSetAncestor> emptyList();
for (final PatchSetAncestor a : descendants) {
changesToGet.add(a.getPatchSet().getParentKey());
}
final Map<Change.Id, Change> m =
db.changes().toMap(db.changes().get(changesToGet));
final ArrayList<ChangeInfo> dependsOn = new ArrayList<ChangeInfo>();
for (final Change.Id a : ancestorOrder) {
final Change ac = m.get(a);
if (ac != null) {
aic.want(ac.getOwner());
dependsOn.add(new ChangeInfo(ac));
}
}
final ArrayList<ChangeInfo> neededBy = new ArrayList<ChangeInfo>();
for (final PatchSetAncestor a : descendants) {
final Change ac = m.get(a.getPatchSet().getParentKey());
if (ac != null) {
aic.want(ac.getOwner());
neededBy.add(new ChangeInfo(ac));
}
}
Collections.sort(neededBy, new Comparator<ChangeInfo>() {
public int compare(final ChangeInfo o1, final ChangeInfo o2) {
return o1.getId().get() - o2.getId().get();
}
});
detail.setDependsOn(dependsOn);
detail.setNeededBy(neededBy);
}
}

View File

@@ -0,0 +1,54 @@
// 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.httpd.rpc.changedetail;
import com.google.gerrit.common.data.ChangeDetail;
import com.google.gerrit.common.data.ChangeDetailService;
import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.common.data.PatchSetPublishDetail;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
class ChangeDetailServiceImpl implements ChangeDetailService {
private final ChangeDetailFactory.Factory changeDetail;
private final PatchSetDetailFactory.Factory patchSetDetail;
private final PatchSetPublishDetailFactory.Factory patchSetPublishDetail;
@Inject
ChangeDetailServiceImpl(final ChangeDetailFactory.Factory changeDetail,
final PatchSetDetailFactory.Factory patchSetDetail,
final PatchSetPublishDetailFactory.Factory patchSetPublishDetail) {
this.changeDetail = changeDetail;
this.patchSetDetail = patchSetDetail;
this.patchSetPublishDetail = patchSetPublishDetail;
}
public void changeDetail(final Change.Id id,
final AsyncCallback<ChangeDetail> callback) {
changeDetail.create(id).to(callback);
}
public void patchSetDetail(final PatchSet.Id id,
final AsyncCallback<PatchSetDetail> callback) {
patchSetDetail.create(id).to(callback);
}
public void patchSetPublishDetail(final PatchSet.Id id,
final AsyncCallback<PatchSetPublishDetail> callback) {
patchSetPublishDetail.create(id).to(callback);
}
}

View File

@@ -0,0 +1,43 @@
// 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.rpc.changedetail;
import com.google.gerrit.common.data.ChangeDetail;
import com.google.gerrit.common.data.ChangeManageService;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
class ChangeManageServiceImpl implements ChangeManageService {
private final SubmitAction.Factory submitAction;
private final AbandonChange.Factory abandonChangeFactory;
@Inject
ChangeManageServiceImpl(final SubmitAction.Factory patchSetAction,
final AbandonChange.Factory abandonChangeFactory) {
this.submitAction = patchSetAction;
this.abandonChangeFactory = abandonChangeFactory;
}
public void submit(final PatchSet.Id patchSetId,
final AsyncCallback<ChangeDetail> cb) {
submitAction.create(patchSetId).to(cb);
}
public void abandonChange(final PatchSet.Id patchSetId, final String message,
final AsyncCallback<ChangeDetail> callback) {
abandonChangeFactory.create(patchSetId, message).to(callback);
}
}

View File

@@ -0,0 +1,41 @@
// 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.rpc.changedetail;
import com.google.gerrit.httpd.rpc.RpcServletModule;
import com.google.gerrit.httpd.rpc.UiRpcModule;
import com.google.gerrit.server.config.FactoryModule;
public class ChangeModule extends RpcServletModule {
public ChangeModule() {
super(UiRpcModule.PREFIX);
}
@Override
protected void configureServlets() {
install(new FactoryModule() {
@Override
protected void configure() {
factory(AbandonChange.Factory.class);
factory(ChangeDetailFactory.Factory.class);
factory(PatchSetDetailFactory.Factory.class);
factory(PatchSetPublishDetailFactory.Factory.class);
factory(SubmitAction.Factory.class);
}
});
rpc(ChangeDetailServiceImpl.class);
rpc(ChangeManageServiceImpl.class);
}
}

View File

@@ -0,0 +1,127 @@
// 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.httpd.rpc.changedetail;
import com.google.gerrit.common.data.PatchSetDetail;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountPatchReview;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** Creates a {@link PatchSetDetail} from a {@link PatchSet}. */
class PatchSetDetailFactory extends Handler<PatchSetDetail> {
interface Factory {
PatchSetDetailFactory create(PatchSet.Id id);
}
private final PatchSetInfoFactory infoFactory;
private final ReviewDb db;
private final PatchListCache patchListCache;
private final ChangeControl.Factory changeControlFactory;
private final PatchSet.Id psId;
private PatchSetDetail detail;
ChangeControl control;
PatchSet patchSet;
@Inject
PatchSetDetailFactory(final PatchSetInfoFactory psif, final ReviewDb db,
final PatchListCache patchListCache,
final ChangeControl.Factory changeControlFactory,
@Assisted final PatchSet.Id id) {
this.infoFactory = psif;
this.db = db;
this.patchListCache = patchListCache;
this.changeControlFactory = changeControlFactory;
this.psId = id;
}
@Override
public PatchSetDetail call() throws OrmException, NoSuchEntityException,
PatchSetInfoNotAvailableException, NoSuchChangeException {
if (control == null || patchSet == null) {
control = changeControlFactory.validateFor(psId.getParentKey());
patchSet = db.patchSets().get(psId);
if (patchSet == null) {
throw new NoSuchEntityException();
}
}
final PatchList list = patchListCache.get(control.getChange(), patchSet);
final List<Patch> patches = list.toPatchList(patchSet.getId());
final Map<Patch.Key, Patch> byKey = new HashMap<Patch.Key, Patch>();
for (final Patch p : patches) {
byKey.put(p.getKey(), p);
}
for (final PatchLineComment c : db.patchComments().published(psId)) {
final Patch p = byKey.get(c.getKey().getParentKey());
if (p != null) {
p.setCommentCount(p.getCommentCount() + 1);
}
}
detail = new PatchSetDetail();
detail.setPatchSet(patchSet);
detail.setInfo(infoFactory.get(psId));
detail.setPatches(patches);
final CurrentUser user = control.getCurrentUser();
if (user instanceof IdentifiedUser) {
// If we are signed in, compute the number of draft comments by the
// current user on each of these patch files. This way they can more
// 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)) {
final Patch p = byKey.get(c.getKey().getParentKey());
if (p != null) {
p.setDraftCount(p.getDraftCount() + 1);
}
}
for (AccountPatchReview r : db.accountPatchReviews().byReviewer(me, psId)) {
final Patch p = byKey.get(r.getKey().getPatchKey());
if (p != null) {
p.setReviewedByCurrentUser(true);
}
}
}
return detail;
}
}

View File

@@ -0,0 +1,155 @@
// 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.rpc.changedetail;
import com.google.gerrit.common.data.AccountInfoCache;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.PatchSetPublishDetail;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.PatchSetInfo;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
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.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
final class PatchSetPublishDetailFactory extends Handler<PatchSetPublishDetail> {
interface Factory {
PatchSetPublishDetailFactory create(PatchSet.Id patchSetId);
}
private final ProjectCache projectCache;
private final PatchSetInfoFactory infoFactory;
private final ApprovalTypes approvalTypes;
private final ReviewDb db;
private final ChangeControl.Factory changeControlFactory;
private final AccountInfoCacheFactory aic;
private final IdentifiedUser user;
private final PatchSet.Id patchSetId;
private AccountInfoCache accounts;
private PatchSetInfo patchSetInfo;
private Change change;
private List<PatchLineComment> drafts;
private Map<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>> allowed;
private Map<ApprovalCategory.Id, PatchSetApproval> given;
@Inject
PatchSetPublishDetailFactory(final PatchSetInfoFactory infoFactory,
final ProjectCache projectCache, 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;
this.changeControlFactory = changeControlFactory;
this.aic = accountInfoCacheFactory.create();
this.user = user;
this.patchSetId = patchSetId;
}
@Override
public PatchSetPublishDetail call() throws OrmException,
PatchSetInfoNotAvailableException, NoSuchChangeException {
final Change.Id changeId = patchSetId.getParentKey();
final ChangeControl control = changeControlFactory.validateFor(changeId);
change = control.getChange();
patchSetInfo = infoFactory.get(patchSetId);
drafts = db.patchComments().draft(patchSetId, user.getAccountId()).toList();
allowed = new HashMap<ApprovalCategory.Id, Set<ApprovalCategoryValue.Id>>();
given = new HashMap<ApprovalCategory.Id, PatchSetApproval>();
if (change.getStatus().isOpen()
&& patchSetId.equals(change.currentPatchSetId())) {
computeAllowed();
for (final PatchSetApproval a : db.patchSetApprovals().byPatchSetUser(
patchSetId, user.getAccountId())) {
given.put(a.getCategoryId(), a);
}
}
aic.want(change.getOwner());
accounts = aic.create();
PatchSetPublishDetail detail = new PatchSetPublishDetail();
detail.setAccounts(accounts);
detail.setPatchSetInfo(patchSetInfo);
detail.setChange(change);
detail.setDrafts(drafts);
detail.setAllowed(allowed);
detail.setGiven(given);
return detail;
}
private void computeAllowed() {
final Set<AccountGroup.Id> am = user.getEffectiveGroups();
final ProjectState pe = projectCache.get(change.getProject());
computeAllowed(am, pe.getLocalRights());
computeAllowed(am, pe.getInheritedRights());
}
private void computeAllowed(final Set<AccountGroup.Id> am,
final Collection<ProjectRight> list) {
for (final ProjectRight r : list) {
if (!am.contains(r.getAccountGroupId())) {
continue;
}
Set<ApprovalCategoryValue.Id> s = allowed.get(r.getApprovalCategoryId());
if (s == null) {
s = new HashSet<ApprovalCategoryValue.Id>();
allowed.put(r.getApprovalCategoryId(), s);
}
final ApprovalType at =
approvalTypes.getApprovalType(r.getApprovalCategoryId());
for (short m = r.getMinValue(); m <= r.getMaxValue(); m++) {
final ApprovalCategoryValue v = at.getValue(m);
if (v != null) {
s.add(v.getId());
}
}
}
}
}

View File

@@ -0,0 +1,158 @@
// 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.rpc.changedetail;
import static com.google.gerrit.reviewdb.ApprovalCategory.SUBMIT;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.ChangeDetail;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.MergeQueue;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.workflow.CategoryFunction;
import com.google.gerrit.server.workflow.FunctionState;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class SubmitAction extends Handler<ChangeDetail> {
interface Factory {
SubmitAction create(PatchSet.Id patchSetId);
}
private final ReviewDb db;
private final MergeQueue merger;
private final ApprovalTypes approvalTypes;
private final FunctionState.Factory functionState;
private final IdentifiedUser user;
private final ChangeDetailFactory.Factory changeDetailFactory;
private final PatchSet.Id patchSetId;
@Inject
SubmitAction(final ReviewDb db, final MergeQueue mq, final ApprovalTypes at,
final FunctionState.Factory fs, final IdentifiedUser user,
final ChangeDetailFactory.Factory changeDetailFactory,
@Assisted final PatchSet.Id patchSetId) {
this.db = db;
this.merger = mq;
this.approvalTypes = at;
this.functionState = fs;
this.user = user;
this.changeDetailFactory = changeDetailFactory;
this.patchSetId = patchSetId;
}
@Override
public ChangeDetail call() throws OrmException, NoSuchEntityException,
IllegalStateException, PatchSetInfoNotAvailableException,
NoSuchChangeException {
final Change change = db.changes().get(patchSetId.getParentKey());
if (change == null) {
throw new NoSuchEntityException();
}
if (!patchSetId.equals(change.currentPatchSetId())) {
throw new IllegalStateException("Patch set " + patchSetId
+ " not current");
}
if (change.getStatus().isClosed()) {
throw new IllegalStateException("Change" + change.getId() + " is closed");
}
final List<PatchSetApproval> allApprovals =
new ArrayList<PatchSetApproval>(db.patchSetApprovals().byPatchSet(
patchSetId).toList());
final PatchSetApproval.Key ak =
new PatchSetApproval.Key(patchSetId, user.getAccountId(), SUBMIT);
PatchSetApproval myAction = null;
boolean isnew = true;
for (final PatchSetApproval ca : allApprovals) {
if (ak.equals(ca.getKey())) {
isnew = false;
myAction = ca;
myAction.setValue((short) 1);
myAction.setGranted();
break;
}
}
if (myAction == null) {
myAction = new PatchSetApproval(ak, (short) 1);
allApprovals.add(myAction);
}
final ApprovalType actionType =
approvalTypes.getApprovalType(myAction.getCategoryId());
if (actionType == null || !actionType.getCategory().isAction()) {
throw new IllegalArgumentException(actionType.getCategory().getName()
+ " not an action");
}
final FunctionState fs =
functionState.create(change, patchSetId, allApprovals);
for (ApprovalType c : approvalTypes.getApprovalTypes()) {
CategoryFunction.forCategory(c.getCategory()).run(c, fs);
}
if (!CategoryFunction.forCategory(actionType.getCategory()).isValid(user,
actionType, fs)) {
throw new IllegalStateException(actionType.getCategory().getName()
+ " not permitted");
}
fs.normalize(actionType, myAction);
if (myAction.getValue() <= 0) {
throw new IllegalStateException(actionType.getCategory().getName()
+ " not permitted");
}
if (change.getStatus() == Change.Status.NEW) {
change.setStatus(Change.Status.SUBMITTED);
ChangeUtil.updated(change);
}
final Transaction txn = db.beginTransaction();
db.changes().update(Collections.singleton(change), txn);
if (change.getStatus().isClosed()) {
db.patchSetApprovals().update(fs.getDirtyChangeApprovals(), txn);
}
if (isnew) {
db.patchSetApprovals().insert(Collections.singleton(myAction), txn);
} else {
db.patchSetApprovals().update(Collections.singleton(myAction), txn);
}
txn.commit();
if (change.getStatus() == Change.Status.SUBMITTED) {
merger.merge(change.getDest());
}
return changeDetailFactory.create(change.getId()).call();
}
}

View File

@@ -0,0 +1,158 @@
// 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.rpc.patch;
import com.google.gerrit.common.data.AddReviewerResult;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.httpd.rpc.changedetail.ChangeDetailFactory;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.mail.AddReviewerSender;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.OrmRunnable;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
class AddReviewer extends Handler<AddReviewerResult> {
interface Factory {
AddReviewer create(Change.Id changeId, Collection<String> nameOrEmails);
}
private final AddReviewerSender.Factory addReviewerSenderFactory;
private final AccountResolver accountResolver;
private final ChangeControl.Factory changeControlFactory;
private final ChangeDetailFactory.Factory changeDetailFactory;
private final ReviewDb db;
private final IdentifiedUser currentUser;
private final IdentifiedUser.GenericFactory identifiedUserFactory;
private final ApprovalCategory.Id addReviewerCategoryId;
private final Change.Id changeId;
private final Collection<String> reviewers;
@Inject
AddReviewer(final AddReviewerSender.Factory addReviewerSenderFactory,
final AccountResolver accountResolver,
final ChangeControl.Factory changeControlFactory, final ReviewDb db,
final IdentifiedUser.GenericFactory identifiedUserFactory,
final IdentifiedUser currentUser, final ApprovalTypes approvalTypes,
final ChangeDetailFactory.Factory changeDetailFactory,
@Assisted final Change.Id changeId,
@Assisted final Collection<String> nameOrEmails) {
this.addReviewerSenderFactory = addReviewerSenderFactory;
this.accountResolver = accountResolver;
this.db = db;
this.changeControlFactory = changeControlFactory;
this.identifiedUserFactory = identifiedUserFactory;
this.currentUser = currentUser;
this.changeDetailFactory = changeDetailFactory;
final List<ApprovalType> allTypes = approvalTypes.getApprovalTypes();
addReviewerCategoryId =
allTypes.get(allTypes.size() - 1).getCategory().getId();
this.changeId = changeId;
this.reviewers = nameOrEmails;
}
@Override
public AddReviewerResult call() throws Exception {
final Set<Account.Id> reviewerIds = new HashSet<Account.Id>();
final ChangeControl control = changeControlFactory.validateFor(changeId);
final AddReviewerResult result = new AddReviewerResult();
for (final String nameOrEmail : reviewers) {
final Account account = accountResolver.find(nameOrEmail);
if (account == null) {
result.addError(new AddReviewerResult.Error(
AddReviewerResult.Error.Type.ACCOUNT_NOT_FOUND, nameOrEmail));
continue;
}
final IdentifiedUser user = identifiedUserFactory.create(account.getId());
if (!control.forUser(user).isVisible()) {
result.addError(new AddReviewerResult.Error(
AddReviewerResult.Error.Type.CHANGE_NOT_VISIBLE, nameOrEmail));
continue;
}
reviewerIds.add(account.getId());
}
if (reviewerIds.isEmpty()) {
return result;
}
// Add the reviewers to the database
//
final Set<Account.Id> added = new HashSet<Account.Id>();
db.run(new OrmRunnable<Object, ReviewDb>() {
public Object run(ReviewDb db, Transaction txn, boolean retry)
throws OrmException {
final PatchSet.Id psid = control.getChange().currentPatchSetId();
for (final Account.Id reviewer : reviewerIds) {
if (!exists(psid, reviewer)) {
// This reviewer has not entered an approval for this change yet.
//
final PatchSetApproval myca = dummyApproval(psid, reviewer);
db.patchSetApprovals().insert(Collections.singleton(myca), txn);
added.add(reviewer);
}
}
return null;
}
});
// Email the reviewers
//
final AddReviewerSender cm;
cm = addReviewerSenderFactory.create(control.getChange());
cm.setFrom(currentUser.getAccountId());
cm.setReviewDb(db);
cm.addReviewers(added);
cm.send();
result.setChange(changeDetailFactory.create(changeId).call());
return result;
}
private boolean exists(final PatchSet.Id patchSetId,
final Account.Id reviewerId) throws OrmException {
return db.patchSetApprovals().byPatchSetUser(patchSetId, reviewerId)
.iterator().hasNext();
}
private PatchSetApproval dummyApproval(final PatchSet.Id patchSetId,
final Account.Id reviewerId) {
return new PatchSetApproval(new PatchSetApproval.Key(patchSetId,
reviewerId, addReviewerCategoryId), (short) 0);
}
}

View File

@@ -0,0 +1,134 @@
// 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.rpc.patch;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.config.Nullable;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class CommentDetailFactory extends Handler<CommentDetail> {
interface Factory {
CommentDetailFactory create(Patch.Key patchKey,
@Assisted("patchSetA") PatchSet.Id patchSetA,
@Assisted("patchSetB") PatchSet.Id patchSetB);
}
private final ReviewDb db;
private final ChangeControl.Factory changeControlFactory;
private final AccountInfoCacheFactory aic;
private final Patch.Key patchKey;
private final PatchSet.Id psa;
private final PatchSet.Id psb;
private final PatchSet.Id patchSetId;
private final Change.Id changeId;
@Inject
CommentDetailFactory(final ReviewDb db,
final ChangeControl.Factory changeControlFactory,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
@Assisted final Patch.Key patchKey,
@Assisted("patchSetA") @Nullable final PatchSet.Id patchSetA,
@Assisted("patchSetB") final PatchSet.Id patchSetB) {
this.db = db;
this.changeControlFactory = changeControlFactory;
this.aic = accountInfoCacheFactory.create();
this.patchKey = patchKey;
this.psa = patchSetA;
this.psb = patchSetB;
patchSetId = patchKey.getParentKey();
changeId = patchSetId.getParentKey();
}
@Override
public CommentDetail call() throws OrmException, NoSuchChangeException {
validatePatchSetId(psa);
validatePatchSetId(psb);
final ChangeControl control = changeControlFactory.validateFor(changeId);
final String pn = patchKey.getFileName();
final CommentDetail r = new CommentDetail(psa, psb);
final List<Patch> historyList = new ArrayList<Patch>();
final Map<PatchSet.Id, Patch> bySet = new HashMap<PatchSet.Id, Patch>();
for (final PatchSet ps : db.patchSets().byChange(changeId)) {
final Patch p = new Patch(new Patch.Key(ps.getId(), pn));
historyList.add(p);
bySet.put(ps.getId(), p);
}
for (PatchLineComment c : db.patchComments().published(changeId, pn)) {
if (r.include(c)) {
aic.want(c.getAuthor());
}
final PatchSet.Id psId = c.getKey().getParentKey().getParentKey();
final Patch patch = bySet.get(psId);
if (patch != null) {
patch.setCommentCount(patch.getCommentCount() + 1);
}
}
final CurrentUser user = control.getCurrentUser();
if (user instanceof IdentifiedUser) {
final Account.Id me = ((IdentifiedUser) user).getAccountId();
for (PatchLineComment c : db.patchComments().draft(changeId, pn, me)) {
if (r.include(c)) {
aic.want(me);
}
final PatchSet.Id psId = c.getKey().getParentKey().getParentKey();
final Patch patch = bySet.get(psId);
if (patch != null) {
patch.setDraftCount(patch.getDraftCount() + 1);
}
}
}
r.setHistory(historyList);
r.setAccountInfoCache(aic.create());
return r;
}
private void validatePatchSetId(final PatchSet.Id psId)
throws NoSuchChangeException {
if (psId == null) { // OK, means use base;
} else if (changeId.equals(psId.getParentKey())) { // OK, same change;
} else {
throw new NoSuchChangeException(changeId);
}
}
}

View File

@@ -0,0 +1,419 @@
// 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.httpd.rpc.patch;
import com.google.gerrit.common.data.AddReviewerResult;
import com.google.gerrit.common.data.ApprovalSummary;
import com.google.gerrit.common.data.ApprovalSummarySet;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.PatchDetailService;
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.PatchScriptSettings;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountPatchReview;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ChangeMessage;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.Patch.Key;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
import com.google.gerrit.server.mail.CommentSender;
import com.google.gerrit.server.mail.EmailException;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
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.workflow.FunctionState;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.OrmRunnable;
import com.google.gwtorm.client.Transaction;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
class PatchDetailServiceImpl extends BaseServiceImplementation implements
PatchDetailService {
private final Logger log = LoggerFactory.getLogger(getClass());
private final CommentSender.Factory commentSenderFactory;
private final PatchSetInfoFactory patchSetInfoFactory;
private final ApprovalTypes approvalTypes;
private final AccountInfoCacheFactory.Factory accountInfoCacheFactory;
private final AddReviewer.Factory addReviewerFactory;
private final ChangeControl.Factory changeControlFactory;
private final CommentDetailFactory.Factory commentDetailFactory;
private final FunctionState.Factory functionStateFactory;
private final PatchScriptFactory.Factory patchScriptFactoryFactory;
private final SaveDraft.Factory saveDraftFactory;
@Inject
PatchDetailServiceImpl(final Provider<ReviewDb> schema,
final Provider<CurrentUser> currentUser,
final CommentSender.Factory commentSenderFactory,
final PatchSetInfoFactory patchSetInfoFactory,
final ApprovalTypes approvalTypes,
final AccountInfoCacheFactory.Factory accountInfoCacheFactory,
final AddReviewer.Factory addReviewerFactory,
final ChangeControl.Factory changeControlFactory,
final CommentDetailFactory.Factory commentDetailFactory,
final FunctionState.Factory functionStateFactory,
final PatchScriptFactory.Factory patchScriptFactoryFactory,
final SaveDraft.Factory saveDraftFactory) {
super(schema, currentUser);
this.patchSetInfoFactory = patchSetInfoFactory;
this.commentSenderFactory = commentSenderFactory;
this.approvalTypes = approvalTypes;
this.accountInfoCacheFactory = accountInfoCacheFactory;
this.addReviewerFactory = addReviewerFactory;
this.changeControlFactory = changeControlFactory;
this.commentDetailFactory = commentDetailFactory;
this.functionStateFactory = functionStateFactory;
this.patchScriptFactoryFactory = patchScriptFactoryFactory;
this.saveDraftFactory = saveDraftFactory;
}
public void patchScript(final Patch.Key patchKey, final PatchSet.Id psa,
final PatchSet.Id psb, final PatchScriptSettings s,
final AsyncCallback<PatchScript> callback) {
if (psb == null) {
callback.onFailure(new NoSuchEntityException());
return;
}
patchScriptFactoryFactory.create(patchKey, psa, psb, s).to(callback);
}
public void patchComments(final Patch.Key patchKey, final PatchSet.Id psa,
final PatchSet.Id psb, final AsyncCallback<CommentDetail> callback) {
if (psb == null) {
callback.onFailure(new NoSuchEntityException());
return;
}
commentDetailFactory.create(patchKey, psa, psb).to(callback);
}
public void saveDraft(final PatchLineComment comment,
final AsyncCallback<PatchLineComment> callback) {
saveDraftFactory.create(comment).to(callback);
}
public void deleteDraft(final PatchLineComment.Key commentKey,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(ReviewDb db) throws OrmException, Failure {
final PatchLineComment comment = db.patchComments().get(commentKey);
if (comment == null) {
throw new Failure(new NoSuchEntityException());
}
if (!getAccountId().equals(comment.getAuthor())) {
throw new Failure(new NoSuchEntityException());
}
if (comment.getStatus() != PatchLineComment.Status.DRAFT) {
throw new Failure(new IllegalStateException("Comment published"));
}
db.patchComments().delete(Collections.singleton(comment));
return VoidResult.INSTANCE;
}
});
}
public void publishComments(final PatchSet.Id psid, final String message,
final Set<ApprovalCategoryValue.Id> approvals,
final AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(ReviewDb db) throws OrmException, Failure {
final PublishResult r;
r = db.run(new OrmRunnable<PublishResult, ReviewDb>() {
public PublishResult run(ReviewDb db, Transaction txn, boolean retry)
throws OrmException {
return doPublishComments(psid, message, approvals, db, txn);
}
});
try {
final CommentSender cm;
cm = commentSenderFactory.create(r.change);
cm.setFrom(getAccountId());
cm.setPatchSet(r.patchSet, patchSetInfoFactory.get(psid));
cm.setChangeMessage(r.message);
cm.setPatchLineComments(r.comments);
cm.setReviewDb(db);
cm.send();
} catch (EmailException e) {
log.error("Cannot send comments by email for patch set " + psid, e);
throw new Failure(e);
} catch (PatchSetInfoNotAvailableException e) {
log.error("Failed to obtain PatchSetInfo for patch set " + psid, e);
throw new Failure(e);
}
return VoidResult.INSTANCE;
}
});
}
/**
* Update the reviewed status for the file by user @code{account}
*/
public void setReviewedByCurrentUser(final Key patchKey,
final boolean reviewed, AsyncCallback<VoidResult> callback) {
run(callback, new Action<VoidResult>() {
public VoidResult run(ReviewDb db) throws OrmException {
Account.Id account = getAccountId();
AccountPatchReview.Key key =
new AccountPatchReview.Key(patchKey, account);
AccountPatchReview apr = db.accountPatchReviews().get(key);
if (apr == null && reviewed) {
db.accountPatchReviews().insert(
Collections.singleton(new AccountPatchReview(patchKey, account)));
} else if (apr != null && !reviewed) {
db.accountPatchReviews().delete(Collections.singleton(apr));
}
return VoidResult.INSTANCE;
}
});
}
private static class PublishResult {
Change change;
PatchSet patchSet;
ChangeMessage message;
List<PatchLineComment> comments;
}
private PublishResult doPublishComments(final PatchSet.Id psid,
final String messageText, final Set<ApprovalCategoryValue.Id> approvals,
final ReviewDb db, final Transaction txn) throws OrmException {
final PublishResult r = new PublishResult();
final Account.Id me = getAccountId();
r.change = db.changes().get(psid.getParentKey());
r.patchSet = db.patchSets().get(psid);
if (r.change == null || r.patchSet == null) {
throw new OrmException(new NoSuchEntityException());
}
final boolean iscurrent = psid.equals(r.change.currentPatchSetId());
r.comments = db.patchComments().draft(psid, me).toList();
for (final PatchLineComment c : r.comments) {
c.setStatus(PatchLineComment.Status.PUBLISHED);
c.updated();
}
db.patchComments().update(r.comments, txn);
final StringBuilder msgbuf = new StringBuilder();
final Map<ApprovalCategory.Id, ApprovalCategoryValue.Id> values =
new HashMap<ApprovalCategory.Id, ApprovalCategoryValue.Id>();
for (final ApprovalCategoryValue.Id v : approvals) {
values.put(v.getParentKey(), v);
}
final boolean applyApprovals = iscurrent && r.change.getStatus().isOpen();
final Map<ApprovalCategory.Id, PatchSetApproval> have =
new HashMap<ApprovalCategory.Id, PatchSetApproval>();
for (PatchSetApproval a : db.patchSetApprovals().byPatchSetUser(psid, me)) {
have.put(a.getCategoryId(), a);
}
for (final ApprovalType at : approvalTypes.getApprovalTypes()) {
final ApprovalCategoryValue.Id v = values.get(at.getCategory().getId());
if (v == null) {
continue;
}
final ApprovalCategoryValue val = at.getValue(v.get());
if (val == null) {
continue;
}
PatchSetApproval mycatpp = have.remove(v.getParentKey());
if (mycatpp == null) {
if (msgbuf.length() > 0) {
msgbuf.append("; ");
}
msgbuf.append(val.getName());
if (applyApprovals) {
mycatpp =
new PatchSetApproval(new PatchSetApproval.Key(psid, me, v
.getParentKey()), v.get());
db.patchSetApprovals().insert(Collections.singleton(mycatpp), txn);
}
} else if (mycatpp.getValue() != v.get()) {
if (msgbuf.length() > 0) {
msgbuf.append("; ");
}
msgbuf.append(val.getName());
if (applyApprovals) {
mycatpp.setValue(v.get());
mycatpp.setGranted();
db.patchSetApprovals().update(Collections.singleton(mycatpp), txn);
}
}
}
if (applyApprovals) {
db.patchSetApprovals().delete(have.values(), txn);
}
if (msgbuf.length() > 0) {
msgbuf.insert(0, "Patch Set " + psid.get() + ": ");
msgbuf.append("\n\n");
} else if (!iscurrent) {
msgbuf.append("Patch Set " + psid.get() + ":\n\n");
}
if (messageText != null) {
msgbuf.append(messageText);
}
if (msgbuf.length() > 0) {
r.message =
new ChangeMessage(new ChangeMessage.Key(r.change.getId(), ChangeUtil
.messageUUID(db)), me);
r.message.setMessage(msgbuf.toString());
db.changeMessages().insert(Collections.singleton(r.message), txn);
}
ChangeUtil.updated(r.change);
db.changes().update(Collections.singleton(r.change), txn);
return r;
}
public void addReviewers(final Change.Id id, final List<String> reviewers,
final AsyncCallback<AddReviewerResult> callback) {
addReviewerFactory.create(id, reviewers).to(callback);
}
public void userApprovals(final Set<Change.Id> cids, final Account.Id aid,
final AsyncCallback<ApprovalSummarySet> callback) {
run(callback, new Action<ApprovalSummarySet>() {
public ApprovalSummarySet run(ReviewDb db)
throws OrmException {
final Map<Change.Id, ApprovalSummary> approvals =
new HashMap<Change.Id, ApprovalSummary>();
final AccountInfoCacheFactory aicFactory =
accountInfoCacheFactory.create();
aicFactory.want(aid);
for (final Change.Id id : cids) {
try {
final ChangeControl cc = changeControlFactory.validateFor(id);
final Change change = cc.getChange();
final PatchSet.Id ps_id = change.currentPatchSetId();
final Map<ApprovalCategory.Id, PatchSetApproval> psas =
new HashMap<ApprovalCategory.Id, PatchSetApproval>();
final FunctionState fs =
functionStateFactory.create(change, ps_id, psas.values());
for (final PatchSetApproval ca : db.patchSetApprovals()
.byPatchSetUser(ps_id, aid)) {
final ApprovalCategory.Id category = ca.getCategoryId();
if (change.getStatus().isOpen()) {
fs.normalize(approvalTypes.getApprovalType(category), ca);
}
if (ca.getValue() == 0
|| ApprovalCategory.SUBMIT.equals(category)) {
continue;
}
psas.put(category, ca);
}
approvals.put(id, new ApprovalSummary(psas.values()));
} catch (NoSuchChangeException nsce) {
/* The user has no access to see this change, so we
* simply do not provide any details about it.
*/
}
}
return new ApprovalSummarySet(aicFactory.create(), approvals);
}
});
}
public void strongestApprovals(final Set<Change.Id> cids,
final AsyncCallback<ApprovalSummarySet> callback) {
run(callback, new Action<ApprovalSummarySet>() {
public ApprovalSummarySet run(ReviewDb db)
throws OrmException {
final Map<Change.Id, ApprovalSummary> approvals =
new HashMap<Change.Id, ApprovalSummary>();
final AccountInfoCacheFactory aicFactory =
accountInfoCacheFactory.create();
for (final Change.Id id : cids) {
try {
final ChangeControl cc = changeControlFactory.validateFor(id);
final Change change = cc.getChange();
final PatchSet.Id ps_id = change.currentPatchSetId();
final Map<ApprovalCategory.Id, PatchSetApproval> psas =
new HashMap<ApprovalCategory.Id, PatchSetApproval>();
final FunctionState fs =
functionStateFactory.create(change, ps_id, psas.values());
for (PatchSetApproval ca : db.patchSetApprovals()
.byPatchSet(ps_id)) {
final ApprovalCategory.Id category = ca.getCategoryId();
if (change.getStatus().isOpen()) {
fs.normalize(approvalTypes.getApprovalType(category), ca);
}
if (ca.getValue() == 0
|| ApprovalCategory.SUBMIT.equals(category)) {
continue;
}
boolean keep = true;
if (psas.containsKey(category)) {
final short oldValue = psas.get(category).getValue();
final short newValue = ca.getValue();
keep = (Math.abs(oldValue) < Math.abs(newValue))
|| ((Math.abs(oldValue) == Math.abs(newValue)
&& (newValue < oldValue)));
}
if (keep) {
aicFactory.want(ca.getAccountId());
psas.put(category, ca);
}
}
approvals.put(id, new ApprovalSummary(psas.values()));
} catch (NoSuchChangeException nsce) {
/* The user has no access to see this change, so we
* simply do not provide any details about it.
*/
}
}
return new ApprovalSummarySet(aicFactory.create(), approvals);
}
});
}
}

View File

@@ -0,0 +1,39 @@
// 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.rpc.patch;
import com.google.gerrit.httpd.rpc.RpcServletModule;
import com.google.gerrit.httpd.rpc.UiRpcModule;
import com.google.gerrit.server.config.FactoryModule;
public class PatchModule extends RpcServletModule {
public PatchModule() {
super(UiRpcModule.PREFIX);
}
@Override
protected void configureServlets() {
install(new FactoryModule() {
@Override
protected void configure() {
factory(AddReviewer.Factory.class);
factory(CommentDetailFactory.Factory.class);
factory(PatchScriptFactory.Factory.class);
factory(SaveDraft.Factory.class);
}
});
rpc(PatchDetailServiceImpl.class);
}
}

View File

@@ -0,0 +1,390 @@
// 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.rpc.patch;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.EditList;
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.PatchScriptSettings;
import com.google.gerrit.common.data.SparseFileContent;
import com.google.gerrit.common.data.PatchScript.DisplayMethod;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.Patch.PatchType;
import com.google.gerrit.server.FileTypeRegistry;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.Text;
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;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class PatchScriptBuilder {
static final int MAX_CONTEXT = 5000000;
static final int BIG_FILE = 9000;
private static final Comparator<Edit> EDIT_SORT = new Comparator<Edit>() {
@Override
public int compare(final Edit o1, final Edit o2) {
return o1.getBeginA() - o2.getBeginA();
}
};
private final List<String> header;
private Repository db;
private Change change;
private PatchScriptSettings settings;
private ObjectId aId;
private ObjectId bId;
private final Side a;
private final Side b;
private List<Edit> edits;
private final FileTypeRegistry registry;
PatchScriptBuilder(final FileTypeRegistry ftr) {
header = new ArrayList<String>();
a = new Side();
b = new Side();
registry = ftr;
}
void setRepository(final Repository r) {
db = r;
}
void setChange(final Change c) {
this.change = c;
}
void setSettings(final PatchScriptSettings s) {
settings = s;
}
void setTrees(final ObjectId a, final ObjectId b) {
aId = a;
bId = b;
}
private int context() {
return settings.getContext();
}
PatchScript toPatchScript(final PatchListEntry contentWS,
final CommentDetail comments, final PatchListEntry contentAct)
throws IOException {
if (contentAct.getPatchType() == PatchType.N_WAY) {
// For a diff --cc format we don't support converting it into
// a patch script. Instead treat everything as a file header.
//
return new PatchScript(change.getKey(), contentAct.getHeaderLines(),
settings, a.dst, b.dst, Collections.<Edit> emptyList(),
a.displayMethod, b.displayMethod);
}
a.path = oldName(contentAct);
b.path = newName(contentAct);
a.resolve(null, aId);
b.resolve(a, bId);
edits = new ArrayList<Edit>(contentAct.getEdits());
ensureCommentsVisible(comments);
header.addAll(contentAct.getHeaderLines());
if (a.mode == FileMode.GITLINK || b.mode == FileMode.GITLINK) {
} else if (a.src == b.src && a.src.size() <= context()
&& contentAct.getEdits().isEmpty()) {
// Odd special case; the files are identical (100% rename or copy)
// and the user has asked for context that is larger than the file.
// Send them the entire file, with an empty edit after the last line.
//
for (int i = 0; i < a.src.size(); i++) {
a.src.addLineTo(a.dst, i);
}
edits = new ArrayList<Edit>(1);
edits.add(new Edit(a.src.size(), a.src.size()));
} else {
if (BIG_FILE < Math.max(a.src.size(), b.src.size()) && 25 < context()) {
settings.setContext(25);
}
packContent();
}
if (contentWS != contentAct) {
// The edit list we used to pack the file contents doesn't honor the
// whitespace settings requested. Instead we must rebuild our edit
// list around the whitespace edit list.
//
edits = new ArrayList<Edit>(contentWS.getEdits());
ensureCommentsVisible(comments);
}
return new PatchScript(change.getKey(), header, settings, a.dst, b.dst,
edits, a.displayMethod, b.displayMethod);
}
private static String oldName(final PatchListEntry entry) {
switch (entry.getChangeType()) {
case ADDED:
return null;
case DELETED:
case MODIFIED:
return entry.getNewName();
case COPIED:
case RENAMED:
default:
return entry.getOldName();
}
}
private static String newName(final PatchListEntry entry) {
switch (entry.getChangeType()) {
case DELETED:
return null;
case ADDED:
case MODIFIED:
case COPIED:
case RENAMED:
default:
return entry.getNewName();
}
}
private void ensureCommentsVisible(final CommentDetail comments) {
if (comments.getCommentsA().isEmpty() && comments.getCommentsB().isEmpty()) {
// No comments, no additional dummy edits are required.
//
return;
}
// Construct empty Edit blocks around each location where a comment is.
// This will force the later packContent method to include the regions
// containing comments, potentially combining those regions together if
// they have overlapping contexts. UI renders will also be able to make
// correct hunks from this, but because the Edit is empty they will not
// style it specially.
//
final List<Edit> empty = new ArrayList<Edit>();
int lastLine;
lastLine = -1;
for (PatchLineComment plc : comments.getCommentsA()) {
final int a = plc.getLine();
if (lastLine != a) {
final int b = mapA2B(a - 1);
if (0 <= b) {
safeAdd(empty, new Edit(a - 1, b));
}
lastLine = a;
}
}
lastLine = -1;
for (PatchLineComment plc : comments.getCommentsB()) {
final int b = plc.getLine();
if (lastLine != b) {
final int a = mapB2A(b - 1);
if (0 <= a) {
safeAdd(empty, new Edit(a, b - 1));
}
lastLine = b;
}
}
// Sort the final list by the index in A, so packContent can combine
// them correctly later.
//
edits.addAll(empty);
Collections.sort(edits, EDIT_SORT);
}
private void safeAdd(final List<Edit> empty, final Edit toAdd) {
final int a = toAdd.getBeginA();
final int b = toAdd.getBeginB();
for (final Edit e : edits) {
if (e.getBeginA() <= a && a <= e.getEndA()) {
return;
}
if (e.getBeginB() <= b && b <= e.getEndB()) {
return;
}
}
empty.add(toAdd);
}
private int mapA2B(final int a) {
if (edits.isEmpty()) {
// Magic special case of an unmodified file.
//
return a;
}
for (int i = 0; i < edits.size(); i++) {
final Edit e = edits.get(i);
if (a < e.getBeginA()) {
if (i == 0) {
// Special case of context at start of file.
//
return a;
}
return e.getBeginB() - (e.getBeginA() - a);
}
if (e.getBeginA() <= a && a <= e.getEndA()) {
return -1;
}
}
final Edit last = edits.get(edits.size() - 1);
return last.getBeginB() + (a - last.getEndA());
}
private int mapB2A(final int b) {
if (edits.isEmpty()) {
// Magic special case of an unmodified file.
//
return b;
}
for (int i = 0; i < edits.size(); i++) {
final Edit e = edits.get(i);
if (b < e.getBeginB()) {
if (i == 0) {
// Special case of context at start of file.
//
return b;
}
return e.getBeginA() - (e.getBeginB() - b);
}
if (e.getBeginB() <= b && b <= e.getEndB()) {
return -1;
}
}
final Edit last = edits.get(edits.size() - 1);
return last.getBeginA() + (b - last.getEndB());
}
private void packContent() {
EditList list = new EditList(edits, context(), a.src.size(), b.src.size());
for (final EditList.Hunk hunk : list.getHunks()) {
while (hunk.next()) {
if (hunk.isContextLine()) {
a.src.addLineTo(a.dst, hunk.getCurA());
hunk.incBoth();
} else if (hunk.isDeletedA()) {
a.src.addLineTo(a.dst, hunk.getCurA());
hunk.incA();
} else if (hunk.isInsertedB()) {
b.src.addLineTo(b.dst, hunk.getCurB());
hunk.incB();
}
}
}
}
private class Side {
String path;
ObjectId id;
FileMode mode;
Text src;
MimeType mimeType = MimeUtil2.UNKNOWN_MIME_TYPE;
DisplayMethod displayMethod = DisplayMethod.DIFF;
final SparseFileContent dst = new SparseFileContent();
void resolve(final Side other, final ObjectId within) throws IOException {
try {
final TreeWalk tw = find(within);
id = tw != null ? tw.getObjectId(0) : ObjectId.zeroId();
mode = tw != null ? tw.getFileMode(0) : FileMode.MISSING;
final boolean reuse =
other != null && other.id.equals(id) && other.mode == mode;
if (reuse) {
src = other.src;
} else if (mode.getObjectType() == Constants.OBJ_BLOB) {
final ObjectLoader ldr = db.openObject(id);
if (ldr == null) {
throw new MissingObjectException(id, Constants.TYPE_BLOB);
}
final byte[] data = ldr.getCachedBytes();
if (ldr.getType() != Constants.OBJ_BLOB) {
throw new IncorrectObjectTypeException(id, Constants.TYPE_BLOB);
}
src = new Text(data);
} else {
src = Text.EMPTY;
}
if (reuse) {
mimeType = other.mimeType;
displayMethod = other.displayMethod;
} else if (src.getContent().length > 0 && FileMode.SYMLINK != mode) {
mimeType = registry.getMimeType(path, src.getContent());
if ("image".equals(mimeType.getMediaType())
&& registry.isSafeInline(mimeType)) {
displayMethod = DisplayMethod.IMG;
}
}
if (mode == FileMode.MISSING) {
displayMethod = DisplayMethod.NONE;
}
dst.setMissingNewlineAtEnd(src.isMissingNewlineAtEnd());
dst.setSize(src.size());
} catch (IOException err) {
throw new IOException("Cannot read " + within.name() + ":" + path, err);
}
}
private TreeWalk find(final ObjectId within) throws MissingObjectException,
IncorrectObjectTypeException, CorruptObjectException, IOException {
if (path == null) {
return null;
}
final RevWalk rw = new RevWalk(db);
final RevTree tree = rw.parseTree(within);
return TreeWalk.forPath(db, path, tree);
}
}
}

View File

@@ -0,0 +1,236 @@
// 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.rpc.patch;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.PatchScriptSettings;
import com.google.gerrit.common.data.PatchScriptSettings.Whitespace;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountGeneralPreferences;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.FileTypeRegistry;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.Nullable;
import com.google.gerrit.server.git.GitRepositoryManager;
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.patch.PatchListKey;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
class PatchScriptFactory extends Handler<PatchScript> {
interface Factory {
PatchScriptFactory create(Patch.Key patchKey,
@Assisted("patchSetA") PatchSet.Id patchSetA,
@Assisted("patchSetB") PatchSet.Id patchSetB,
PatchScriptSettings settings);
}
private static final Logger log =
LoggerFactory.getLogger(PatchScriptFactory.class);
private final GitRepositoryManager repoManager;
private final FileTypeRegistry registry;
private final PatchListCache patchListCache;
private final ReviewDb db;
private final ChangeControl.Factory changeControlFactory;
private final Patch.Key patchKey;
@Nullable
private final PatchSet.Id psa;
private final PatchSet.Id psb;
private final PatchScriptSettings settings;
private final PatchSet.Id patchSetId;
private final Change.Id changeId;
private Change change;
private PatchSet patchSet;
private Project.NameKey projectKey;
private Repository git;
private ChangeControl control;
private ObjectId aId;
private ObjectId bId;
@Inject
PatchScriptFactory(final GitRepositoryManager grm, final FileTypeRegistry ftr,
final PatchListCache patchListCache, final ReviewDb db,
final ChangeControl.Factory changeControlFactory,
@Assisted final Patch.Key patchKey,
@Assisted("patchSetA") @Nullable final PatchSet.Id patchSetA,
@Assisted("patchSetB") final PatchSet.Id patchSetB,
@Assisted final PatchScriptSettings settings) {
this.repoManager = grm;
this.registry = ftr;
this.patchListCache = patchListCache;
this.db = db;
this.changeControlFactory = changeControlFactory;
this.patchKey = patchKey;
this.psa = patchSetA;
this.psb = patchSetB;
this.settings = settings;
patchSetId = patchKey.getParentKey();
changeId = patchSetId.getParentKey();
}
@Override
public PatchScript call() throws OrmException, NoSuchChangeException {
validatePatchSetId(psa);
validatePatchSetId(psb);
control = changeControlFactory.validateFor(changeId);
change = control.getChange();
patchSet = db.patchSets().get(patchSetId);
if (patchSet == null) {
throw new NoSuchChangeException(changeId);
}
projectKey = change.getProject();
aId = psa != null ? toObjectId(db, psa) : null;
bId = toObjectId(db, psb);
try {
git = repoManager.openRepository(projectKey.get());
} catch (RepositoryNotFoundException e) {
log.error("Repository " + projectKey + " not found", e);
throw new NoSuchChangeException(changeId, e);
}
final String fileName = patchKey.getFileName();
try {
final PatchList list = listFor(keyFor(settings.getWhitespace()));
final PatchScriptBuilder b = newBuilder(list);
final PatchListEntry contentWS = list.get(fileName);
final CommentDetail comments = allComments(db);
final PatchListEntry contentActual;
if (settings.getWhitespace() == Whitespace.IGNORE_NONE) {
contentActual = contentWS;
} else {
// If we are ignoring whitespace in some form, we still need to know
// where the post-image differs so we can ensure the post-image lines
// are still packed for the client to display.
//
contentActual = listFor(keyFor(Whitespace.IGNORE_NONE)).get(fileName);
}
try {
return b.toPatchScript(contentWS, comments, contentActual);
} catch (IOException e) {
log.error("File content unavailable", e);
throw new NoSuchChangeException(changeId, e);
}
} finally {
git.close();
}
}
private PatchListKey keyFor(final Whitespace whitespace) {
return new PatchListKey(projectKey, aId, bId, whitespace);
}
private PatchList listFor(final PatchListKey key) {
return patchListCache.get(key);
}
private PatchScriptBuilder newBuilder(final PatchList list)
throws NoSuchChangeException {
final PatchScriptSettings s = new PatchScriptSettings(settings);
final int ctx = settings.getContext();
if (ctx == AccountGeneralPreferences.WHOLE_FILE_CONTEXT)
s.setContext(PatchScriptBuilder.MAX_CONTEXT);
else if (0 <= ctx && ctx <= PatchScriptBuilder.MAX_CONTEXT)
s.setContext(ctx);
else
throw new NoSuchChangeException(changeId);
final PatchScriptBuilder b = new PatchScriptBuilder(registry);
b.setRepository(git);
b.setChange(change);
b.setSettings(s);
b.setTrees(list.getOldId(), list.getNewId());
return b;
}
private ObjectId toObjectId(final ReviewDb db, final PatchSet.Id psId)
throws OrmException, NoSuchChangeException {
if (!changeId.equals(psId.getParentKey())) {
throw new NoSuchChangeException(changeId);
}
final PatchSet ps = db.patchSets().get(psId);
if (ps == null || ps.getRevision() == null
|| ps.getRevision().get() == null) {
throw new NoSuchChangeException(changeId);
}
try {
return ObjectId.fromString(ps.getRevision().get());
} catch (IllegalArgumentException e) {
log.error("Patch set " + psId + " has invalid revision");
throw new NoSuchChangeException(changeId, e);
}
}
private void validatePatchSetId(final PatchSet.Id psId)
throws NoSuchChangeException {
if (psId == null) { // OK, means use base;
} else if (changeId.equals(psId.getParentKey())) { // OK, same change;
} else {
throw new NoSuchChangeException(changeId);
}
}
private CommentDetail allComments(final ReviewDb db) throws OrmException {
final CommentDetail r = new CommentDetail(psa, psb);
final String pn = patchKey.get();
for (PatchLineComment p : db.patchComments().published(changeId, pn)) {
r.include(p);
}
if (control.getCurrentUser() instanceof IdentifiedUser) {
for (PatchLineComment p : db.patchComments().draft(changeId, pn,
((IdentifiedUser) control.getCurrentUser()).getAccountId())) {
r.include(p);
}
}
return r;
}
}

View File

@@ -0,0 +1,103 @@
// 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.rpc.patch;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Patch;
import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Collections;
class SaveDraft extends Handler<PatchLineComment> {
interface Factory {
SaveDraft create(PatchLineComment comment);
}
private final ChangeControl.Factory changeControlFactory;
private final ReviewDb db;
private final IdentifiedUser currentUser;
private final PatchLineComment comment;
@Inject
SaveDraft(final ChangeControl.Factory changeControlFactory,
final ReviewDb db, final IdentifiedUser currentUser,
@Assisted final PatchLineComment comment) {
this.changeControlFactory = changeControlFactory;
this.db = db;
this.currentUser = currentUser;
this.comment = comment;
}
@Override
public PatchLineComment call() throws NoSuchChangeException, OrmException {
if (comment.getStatus() != PatchLineComment.Status.DRAFT) {
throw new IllegalStateException("Comment published");
}
final Patch.Key patchKey = comment.getKey().getParentKey();
final PatchSet.Id patchSetId = patchKey.getParentKey();
final Change.Id changeId = patchKey.getParentKey().getParentKey();
changeControlFactory.validateFor(changeId);
if (db.patchSets().get(patchSetId) == null) {
throw new NoSuchChangeException(changeId);
}
final Account.Id me = currentUser.getAccountId();
if (comment.getKey().get() == null) {
if (comment.getLine() < 1) {
throw new IllegalStateException("Comment line must be >= 1, not "
+ comment.getLine());
}
if (comment.getParentUuid() != null) {
final PatchLineComment parent =
db.patchComments().get(
new PatchLineComment.Key(patchKey, comment.getParentUuid()));
if (parent == null || parent.getSide() != comment.getSide()) {
throw new IllegalStateException("Parent comment must be on same side");
}
}
final PatchLineComment nc =
new PatchLineComment(new PatchLineComment.Key(patchKey, ChangeUtil
.messageUUID(db)), comment.getLine(), me, comment.getParentUuid());
nc.setSide(comment.getSide());
nc.setMessage(comment.getMessage());
db.patchComments().insert(Collections.singleton(nc));
return nc;
} else {
if (!me.equals(comment.getAuthor())) {
throw new NoSuchChangeException(changeId);
}
comment.updated();
db.patchComments().update(Collections.singleton(comment));
return comment;
}
}
}

View File

@@ -0,0 +1,185 @@
// 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.rpc.project;
import com.google.gerrit.common.errors.InvalidNameException;
import com.google.gerrit.common.errors.InvalidRevisionException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.ObjectWalk;
import org.eclipse.jgit.revwalk.RevWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
class AddBranch extends Handler<List<Branch>> {
private static final Logger log = LoggerFactory.getLogger(AddBranch.class);
interface Factory {
AddBranch create(@Assisted Project.NameKey projectName,
@Assisted("branchName") String branchName,
@Assisted("startingRevision") String startingRevision);
}
private final ProjectControl.Factory projectControlFactory;
private final ListBranches.Factory listBranchesFactory;
private final IdentifiedUser identifiedUser;
private final GitRepositoryManager repoManager;
private final ReplicationQueue replication;
private final Project.NameKey projectName;
private final String branchName;
private final String startingRevision;
@Inject
AddBranch(final ProjectControl.Factory projectControlFactory,
final ListBranches.Factory listBranchesFactory,
final IdentifiedUser identifiedUser, final GitRepositoryManager repoManager,
final ReplicationQueue replication,
@Assisted Project.NameKey projectName,
@Assisted("branchName") String branchName,
@Assisted("startingRevision") String startingRevision) {
this.projectControlFactory = projectControlFactory;
this.listBranchesFactory = listBranchesFactory;
this.identifiedUser = identifiedUser;
this.repoManager = repoManager;
this.replication = replication;
this.projectName = projectName;
this.branchName = branchName;
this.startingRevision = startingRevision;
}
@Override
public List<Branch> call() throws NoSuchProjectException,
InvalidNameException, InvalidRevisionException, IOException {
final ProjectControl projectControl =
projectControlFactory.validateFor(projectName, ProjectControl.OWNER
| ProjectControl.VISIBLE);
String refname = branchName;
while (refname.startsWith("/")) {
refname = refname.substring(1);
}
if (!refname.startsWith(Constants.R_REFS)) {
refname = Constants.R_HEADS + refname;
}
if (!Repository.isValidRefName(refname)) {
throw new InvalidNameException();
}
if (!projectControl.canCreateRef(refname)) {
throw new IllegalStateException("Cannot create " + refname);
}
final Branch.NameKey name = new Branch.NameKey(projectName, refname);
final Repository repo = repoManager.openRepository(projectName.get());
try {
final ObjectId revid = parseStartingRevision(repo);
final RevWalk rw = verifyConnected(repo, revid);
try {
final RefUpdate u = repo.updateRef(refname);
u.setExpectedOldObjectId(ObjectId.zeroId());
u.setNewObjectId(revid);
u.setRefLogIdent(identifiedUser.newPersonIdent());
u.setRefLogMessage("created via web from " + startingRevision, false);
final RefUpdate.Result result = u.update(rw);
switch (result) {
case FAST_FORWARD:
case NEW:
case NO_CHANGE:
replication.scheduleUpdate(name.getParentKey(), refname);
break;
default: {
final String msg =
"Cannot create branch " + name + ": " + result.name();
log.error(msg);
throw new IOException(result.name());
}
}
} catch (IOException err) {
log.error("Cannot create branch " + name, err);
throw err;
}
} finally {
repo.close();
}
return listBranchesFactory.create(projectName).call();
}
private ObjectId parseStartingRevision(final Repository repo)
throws InvalidRevisionException {
try {
final ObjectId revid = repo.resolve(startingRevision);
if (revid == null) {
throw new InvalidRevisionException();
}
return revid;
} catch (IOException err) {
log.error("Cannot resolve \"" + startingRevision + "\" in project \""
+ projectName + "\"", err);
throw new InvalidRevisionException();
}
}
private RevWalk verifyConnected(final Repository repo, final ObjectId revid)
throws InvalidRevisionException {
try {
final ObjectWalk rw = new ObjectWalk(repo);
try {
rw.markStart(rw.parseCommit(revid));
} catch (IncorrectObjectTypeException err) {
throw new InvalidRevisionException();
}
for (final Ref r : repo.getAllRefs().values()) {
try {
rw.markUninteresting(rw.parseAny(r.getObjectId()));
} catch (MissingObjectException err) {
continue;
}
}
rw.checkConnectivity();
return rw;
} catch (IncorrectObjectTypeException err) {
throw new InvalidRevisionException();
} catch (MissingObjectException err) {
throw new InvalidRevisionException();
} catch (IOException err) {
log.error("Repository \"" + repo.getDirectory()
+ "\" may be corrupt; suggest running git fsck", err);
throw new InvalidRevisionException();
}
}
}

View File

@@ -0,0 +1,129 @@
// 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.rpc.project;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.ProjectDetail;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.account.NoSuchGroupException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Collections;
class AddProjectRight extends Handler<ProjectDetail> {
interface Factory {
AddProjectRight create(@Assisted Project.NameKey projectName,
@Assisted ApprovalCategory.Id categoryId, @Assisted String groupName,
@Assisted("min") short min, @Assisted("max") short max);
}
private final ProjectDetailFactory.Factory projectDetailFactory;
private final ProjectControl.Factory projectControlFactory;
private final ProjectCache projectCache;
private final ReviewDb db;
private final ApprovalTypes approvalTypes;
private final Project.NameKey projectName;
private final ApprovalCategory.Id categoryId;
private final AccountGroup.NameKey groupName;
private final short min;
private final short max;
@Inject
AddProjectRight(final ProjectDetailFactory.Factory projectDetailFactory,
final ProjectControl.Factory projectControlFactory,
final ProjectCache projectCache, final ReviewDb db,
final ApprovalTypes approvalTypes,
@Assisted final Project.NameKey projectName,
@Assisted final ApprovalCategory.Id categoryId,
@Assisted final String groupName, @Assisted("min") final short min,
@Assisted("max") final short max) {
this.projectDetailFactory = projectDetailFactory;
this.projectControlFactory = projectControlFactory;
this.projectCache = projectCache;
this.approvalTypes = approvalTypes;
this.db = db;
this.projectName = projectName;
this.categoryId = categoryId;
this.groupName = new AccountGroup.NameKey(groupName);
if (min <= max) {
this.min = min;
this.max = max;
} else {
this.min = max;
this.max = min;
}
}
@Override
public ProjectDetail call() throws NoSuchProjectException, OrmException,
NoSuchGroupException {
final ProjectControl projectControl =
projectControlFactory.ownerFor(projectName);
if (projectControl.getProjectState().isSpecialWildProject()
&& ApprovalCategory.OWN.equals(categoryId)) {
// Giving out control of the WILD_PROJECT to other groups beyond
// Administrators is dangerous. Having control over WILD_PROJECT
// is about the same as having Administrator access as users are
// able to affect grants in all projects on the system.
//
throw new IllegalArgumentException("Cannot give " + categoryId.get()
+ " on " + projectName + " " + groupName);
}
final ApprovalType at = approvalTypes.getApprovalType(categoryId);
if (at == null || at.getValue(min) == null || at.getValue(max) == null) {
throw new IllegalArgumentException("Invalid category " + categoryId
+ " or range " + min + ".." + max);
}
final AccountGroup group = db.accountGroups().get(groupName);
if (group == null) {
throw new NoSuchGroupException(groupName);
}
final ProjectRight.Key key =
new ProjectRight.Key(projectName, categoryId, group.getId());
ProjectRight pr = db.projectRights().get(key);
if (pr == null) {
pr = new ProjectRight(key);
pr.setMinValue(min);
pr.setMaxValue(max);
db.projectRights().insert(Collections.singleton(pr));
} else {
pr.setMinValue(min);
pr.setMaxValue(max);
db.projectRights().update(Collections.singleton(pr));
}
projectCache.evict(projectControl.getProject());
return projectDetailFactory.create(projectName).call();
}
}

View File

@@ -0,0 +1,81 @@
// 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.rpc.project;
import com.google.gerrit.common.data.ProjectDetail;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Collections;
class ChangeProjectSettings extends Handler<ProjectDetail> {
interface Factory {
ChangeProjectSettings create(@Assisted Project update);
}
private final ProjectDetailFactory.Factory projectDetailFactory;
private final ProjectControl.Factory projectControlFactory;
private final ProjectCache projectCache;
private final ReviewDb db;
private final GitRepositoryManager repoManager;
private final Project update;
@Inject
ChangeProjectSettings(
final ProjectDetailFactory.Factory projectDetailFactory,
final ProjectControl.Factory projectControlFactory,
final ProjectCache projectCache, final ReviewDb db,
final GitRepositoryManager grm,
@Assisted final Project update) {
this.projectDetailFactory = projectDetailFactory;
this.projectControlFactory = projectControlFactory;
this.projectCache = projectCache;
this.db = db;
this.repoManager = grm;
this.update = update;
}
@Override
public ProjectDetail call() throws NoSuchProjectException, OrmException {
final Project.NameKey projectName = update.getNameKey();
final ProjectControl projectControl =
projectControlFactory.ownerFor(projectName);
final Project proj = db.projects().get(projectName);
if (proj == null) {
throw new NoSuchProjectException(projectName);
}
proj.copySettingsFrom(update);
db.projects().update(Collections.singleton(proj));
projectCache.evict(proj);
if (!projectControl.getProjectState().isSpecialWildProject()) {
repoManager.setProjectDescription(projectName.get(), update.getDescription());
}
return projectDetailFactory.create(projectName).call();
}
}

View File

@@ -0,0 +1,121 @@
// 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.rpc.project;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
class DeleteBranches extends Handler<Set<Branch.NameKey>> {
private static final Logger log =
LoggerFactory.getLogger(DeleteBranches.class);
interface Factory {
DeleteBranches create(@Assisted Project.NameKey name,
@Assisted Set<Branch.NameKey> toRemove);
}
private final ProjectControl.Factory projectControlFactory;
private final GitRepositoryManager repoManager;
private final ReplicationQueue replication;
private final Project.NameKey projectName;
private final Set<Branch.NameKey> toRemove;
@Inject
DeleteBranches(final ProjectControl.Factory projectControlFactory,
final GitRepositoryManager repoManager,
final ReplicationQueue replication,
@Assisted Project.NameKey name, @Assisted Set<Branch.NameKey> toRemove) {
this.projectControlFactory = projectControlFactory;
this.repoManager = repoManager;
this.replication = replication;
this.projectName = name;
this.toRemove = toRemove;
}
@Override
public Set<Branch.NameKey> call() throws NoSuchProjectException,
RepositoryNotFoundException {
final ProjectControl projectControl =
projectControlFactory.validateFor(projectName, ProjectControl.OWNER
| ProjectControl.VISIBLE);
for (Branch.NameKey k : toRemove) {
if (!projectName.equals(k.getParentKey())) {
throw new IllegalArgumentException("All keys must be from same project");
}
if (!projectControl.canDeleteRef(k.get())) {
throw new IllegalStateException("Cannot delete " + k.getShortName());
}
}
final Set<Branch.NameKey> deleted = new HashSet<Branch.NameKey>();
final Repository r = repoManager.openRepository(projectName.get());
try {
for (final Branch.NameKey branchKey : toRemove) {
final String refname = branchKey.get();
final RefUpdate.Result result;
try {
final RefUpdate u = r.updateRef(refname);
u.setForceUpdate(true);
result = u.delete();
} catch (IOException e) {
log.error("Cannot delete " + branchKey, e);
continue;
}
switch (result) {
case NEW:
case NO_CHANGE:
case FAST_FORWARD:
case FORCED:
deleted.add(branchKey);
replication.scheduleUpdate(projectName, refname);
break;
case REJECTED_CURRENT_BRANCH:
log.warn("Cannot delete " + branchKey + ": " + result.name());
break;
default:
log.error("Cannot delete " + branchKey + ": " + result.name());
break;
}
}
} finally {
r.close();
}
return deleted;
}
}

View File

@@ -0,0 +1,79 @@
// 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.rpc.project;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Collections;
import java.util.Set;
class DeleteProjectRights extends Handler<VoidResult> {
interface Factory {
DeleteProjectRights create(@Assisted Project.NameKey projectName,
@Assisted Set<ProjectRight.Key> toRemove);
}
private final ProjectControl.Factory projectControlFactory;
private final ProjectCache projectCache;
private final ReviewDb db;
private final Project.NameKey projectName;
private final Set<ProjectRight.Key> toRemove;
@Inject
DeleteProjectRights(final ProjectControl.Factory projectControlFactory,
final ProjectCache projectCache, final ReviewDb db,
@Assisted final Project.NameKey projectName,
@Assisted final Set<ProjectRight.Key> toRemove) {
this.projectControlFactory = projectControlFactory;
this.projectCache = projectCache;
this.db = db;
this.projectName = projectName;
this.toRemove = toRemove;
}
@Override
public VoidResult call() throws NoSuchProjectException, OrmException {
final ProjectControl projectControl =
projectControlFactory.ownerFor(projectName);
for (final ProjectRight.Key k : toRemove) {
if (!projectName.equals(k.getProjectNameKey())) {
throw new IllegalArgumentException("All keys must be from same project");
}
}
for (final ProjectRight.Key k : toRemove) {
final ProjectRight m = db.projectRights().get(k);
if (m != null) {
db.projectRights().delete(Collections.singleton(m));
}
}
projectCache.evict(projectControl.getProject());
return VoidResult.INSTANCE;
}
}

View File

@@ -0,0 +1,108 @@
// 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.rpc.project;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RevId;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
class ListBranches extends Handler<List<Branch>> {
interface Factory {
ListBranches create(@Assisted Project.NameKey name);
}
private final ProjectControl.Factory projectControlFactory;
private final GitRepositoryManager repoManager;
private final Project.NameKey projectName;
@Inject
ListBranches(final ProjectControl.Factory projectControlFactory,
final GitRepositoryManager repoManager,
@Assisted final Project.NameKey name) {
this.projectControlFactory = projectControlFactory;
this.repoManager = repoManager;
this.projectName = name;
}
@Override
public List<Branch> call() throws NoSuchProjectException,
RepositoryNotFoundException {
final ProjectControl projectControl =
projectControlFactory.validateFor(projectName, ProjectControl.OWNER
| ProjectControl.VISIBLE);
final List<Branch> branches = new ArrayList<Branch>();
final Repository db = repoManager.openRepository(projectName.get());
try {
final Map<String, Ref> all = db.getAllRefs();
if (!all.containsKey(Constants.HEAD)) {
// The branch pointed to by HEAD doesn't exist yet. Fake
// that it exists by returning a Ref with no ObjectId.
//
try {
final String head = db.getFullBranch();
if (head != null && head.startsWith(Constants.R_REFS)) {
all.put(Constants.HEAD, new Ref(Ref.Storage.LOOSE, Constants.HEAD,
head, null));
}
} catch (IOException e) {
// Ignore the failure reading HEAD.
}
}
for (final Ref ref : all.values()) {
final String name = ref.getName();
if (name.startsWith(Constants.R_HEADS)) {
final Branch b = new Branch(new Branch.NameKey(projectName, name));
if (ref.getObjectId() != null) {
b.setRevision(new RevId(ref.getObjectId().name()));
}
branches.add(b);
}
}
} finally {
db.close();
}
Collections.sort(branches, new Comparator<Branch>() {
@Override
public int compare(final Branch a, final Branch b) {
return a.getName().compareTo(b.getName());
}
});
return branches;
}
}

View File

@@ -0,0 +1,84 @@
// 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.rpc.project;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
class OwnedProjects extends Handler<List<Project>> {
interface Factory {
OwnedProjects create();
}
private final ProjectControl.Factory projectControlFactory;
private final IdentifiedUser user;
private final ReviewDb db;
@Inject
OwnedProjects(final ProjectControl.Factory projectControlFactory,
final IdentifiedUser user, final ReviewDb db) {
this.projectControlFactory = projectControlFactory;
this.user = user;
this.db = db;
}
@Override
public List<Project> call() throws OrmException {
final List<Project> result;
if (user.isAdministrator()) {
result = db.projects().all().toList();
} else {
final HashSet<Project.NameKey> seen = new HashSet<Project.NameKey>();
result = new ArrayList<Project>();
for (final AccountGroup.Id groupId : user.getEffectiveGroups()) {
for (final ProjectRight r : db.projectRights().byCategoryGroup(
ApprovalCategory.OWN, groupId)) {
final Project.NameKey name = r.getProjectNameKey();
if (!seen.add(name)) {
continue;
}
try {
ProjectControl c = projectControlFactory.ownerFor(name);
result.add(c.getProject());
} catch (NoSuchProjectException e) {
continue;
}
}
}
}
Collections.sort(result, new Comparator<Project>() {
public int compare(final Project a, final Project b) {
return a.getName().compareTo(b.getName());
}
});
return result;
}
}

View File

@@ -0,0 +1,104 @@
// 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.httpd.rpc.project;
import com.google.gerrit.common.data.ProjectAdminService;
import com.google.gerrit.common.data.ProjectDetail;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.inject.Inject;
import java.util.List;
import java.util.Set;
class ProjectAdminServiceImpl implements ProjectAdminService {
private final AddBranch.Factory addBranchFactory;
private final AddProjectRight.Factory addProjectRightFactory;
private final ChangeProjectSettings.Factory changeProjectSettingsFactory;
private final DeleteBranches.Factory deleteBranchesFactory;
private final DeleteProjectRights.Factory deleteProjectRightsFactory;
private final ListBranches.Factory listBranchesFactory;
private final OwnedProjects.Factory ownedProjectsFactory;
private final ProjectDetailFactory.Factory projectDetailFactory;
@Inject
ProjectAdminServiceImpl(final AddBranch.Factory addBranchFactory,
final AddProjectRight.Factory addProjectRightFactory,
final ChangeProjectSettings.Factory changeProjectSettingsFactory,
final DeleteBranches.Factory deleteBranchesFactory,
final DeleteProjectRights.Factory deleteProjectRightFactory,
final ListBranches.Factory listBranchesFactory,
final OwnedProjects.Factory ownedProjectsFactory,
final ProjectDetailFactory.Factory projectDetailFactory) {
this.addBranchFactory = addBranchFactory;
this.addProjectRightFactory = addProjectRightFactory;
this.changeProjectSettingsFactory = changeProjectSettingsFactory;
this.deleteBranchesFactory = deleteBranchesFactory;
this.deleteProjectRightsFactory = deleteProjectRightFactory;
this.listBranchesFactory = listBranchesFactory;
this.ownedProjectsFactory = ownedProjectsFactory;
this.projectDetailFactory = projectDetailFactory;
}
public void ownedProjects(final AsyncCallback<List<Project>> callback) {
ownedProjectsFactory.create().to(callback);
}
public void projectDetail(final Project.NameKey projectName,
final AsyncCallback<ProjectDetail> callback) {
projectDetailFactory.create(projectName).to(callback);
}
public void changeProjectSettings(final Project update,
final AsyncCallback<ProjectDetail> callback) {
changeProjectSettingsFactory.create(update).to(callback);
}
public void deleteRight(final Project.NameKey projectName,
final Set<ProjectRight.Key> toRemove,
final AsyncCallback<VoidResult> callback) {
deleteProjectRightsFactory.create(projectName, toRemove).to(callback);
}
public void addRight(final Project.NameKey projectName,
final ApprovalCategory.Id categoryId, final String groupName,
final short min, final short max,
final AsyncCallback<ProjectDetail> callback) {
addProjectRightFactory.create(projectName, categoryId, groupName, min, max)
.to(callback);
}
public void listBranches(final Project.NameKey projectName,
final AsyncCallback<List<Branch>> callback) {
listBranchesFactory.create(projectName).to(callback);
}
public void deleteBranch(final Project.NameKey projectName,
final Set<Branch.NameKey> toRemove,
final AsyncCallback<Set<Branch.NameKey>> callback) {
deleteBranchesFactory.create(projectName, toRemove).to(callback);
}
public void addBranch(final Project.NameKey projectName,
final String branchName, final String startingRevision,
final AsyncCallback<List<Branch>> callback) {
addBranchFactory.create(projectName, branchName, startingRevision).to(
callback);
}
}

View File

@@ -0,0 +1,125 @@
// 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.rpc.project;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.ProjectDetail;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
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;
class ProjectDetailFactory extends Handler<ProjectDetail> {
interface Factory {
ProjectDetailFactory create(@Assisted Project.NameKey name);
}
private final ApprovalTypes approvalTypes;
private final GroupCache groupCache;
private final ProjectControl.Factory projectControlFactory;
private final Project.NameKey projectName;
private Map<AccountGroup.Id, AccountGroup> groups;
@Inject
ProjectDetailFactory(final ApprovalTypes approvalTypes,
final GroupCache groupCache,
final ProjectControl.Factory projectControlFactory,
@Assisted final Project.NameKey name) {
this.approvalTypes = approvalTypes;
this.groupCache = groupCache;
this.projectControlFactory = projectControlFactory;
this.projectName = name;
}
@Override
public ProjectDetail call() throws NoSuchProjectException {
final ProjectState projectState =
projectControlFactory.validateFor(projectName,
ProjectControl.OWNER | ProjectControl.VISIBLE).getProjectState();
final ProjectDetail detail = new ProjectDetail();
detail.setProject(projectState.getProject());
groups = new HashMap<AccountGroup.Id, AccountGroup>();
final List<ProjectRight> rights = new ArrayList<ProjectRight>();
for (final ProjectRight p : projectState.getLocalRights()) {
rights.add(p);
wantGroup(p.getAccountGroupId());
}
for (final ProjectRight p : projectState.getInheritedRights()) {
rights.add(p);
wantGroup(p.getAccountGroupId());
}
loadGroups();
Collections.sort(rights, new Comparator<ProjectRight>() {
@Override
public int compare(final ProjectRight a, final ProjectRight b) {
int rc = categoryOf(a).compareTo(categoryOf(b));
if (rc == 0) {
rc = groupOf(a).compareTo(groupOf(b));
}
return rc;
}
private String categoryOf(final ProjectRight r) {
final ApprovalType type =
approvalTypes.getApprovalType(r.getApprovalCategoryId());
if (type == null) {
return r.getApprovalCategoryId().get();
}
return type.getCategory().getName();
}
private String groupOf(final ProjectRight r) {
return groups.get(r.getAccountGroupId()).getName();
}
});
detail.setRights(rights);
detail.setGroups(groups);
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));
}
}
}

View File

@@ -0,0 +1,43 @@
// 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.rpc.project;
import com.google.gerrit.httpd.rpc.RpcServletModule;
import com.google.gerrit.httpd.rpc.UiRpcModule;
import com.google.gerrit.server.config.FactoryModule;
public class ProjectModule extends RpcServletModule {
public ProjectModule() {
super(UiRpcModule.PREFIX);
}
@Override
protected void configureServlets() {
install(new FactoryModule() {
@Override
protected void configure() {
factory(AddBranch.Factory.class);
factory(AddProjectRight.Factory.class);
factory(ChangeProjectSettings.Factory.class);
factory(DeleteBranches.Factory.class);
factory(DeleteProjectRights.Factory.class);
factory(ListBranches.Factory.class);
factory(OwnedProjects.Factory.class);
factory(ProjectDetailFactory.Factory.class);
}
});
rpc(ProjectAdminServiceImpl.class);
}
}