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:
4
gerrit-httpd/.gitignore
vendored
Normal file
4
gerrit-httpd/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/target
|
||||
/.classpath
|
||||
/.project
|
||||
/.settings/org.maven.ide.eclipse.prefs
|
||||
3
gerrit-httpd/.settings/org.eclipse.core.resources.prefs
Normal file
3
gerrit-httpd/.settings/org.eclipse.core.resources.prefs
Normal file
@@ -0,0 +1,3 @@
|
||||
#Tue Sep 02 16:59:24 PDT 2008
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
||||
3
gerrit-httpd/.settings/org.eclipse.core.runtime.prefs
Normal file
3
gerrit-httpd/.settings/org.eclipse.core.runtime.prefs
Normal file
@@ -0,0 +1,3 @@
|
||||
#Tue Sep 02 16:59:24 PDT 2008
|
||||
eclipse.preferences.version=1
|
||||
line.separator=\n
|
||||
268
gerrit-httpd/.settings/org.eclipse.jdt.core.prefs
Normal file
268
gerrit-httpd/.settings/org.eclipse.jdt.core.prefs
Normal 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
|
||||
61
gerrit-httpd/.settings/org.eclipse.jdt.ui.prefs
Normal file
61
gerrit-httpd/.settings/org.eclipse.jdt.ui.prefs
Normal 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
67
gerrit-httpd/pom.xml
Normal 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>
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 <input type="hidden"> 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<Result> {
|
||||
* interface Factory {
|
||||
* Foo create(... args ...);
|
||||
* }
|
||||
* @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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user