diff --git a/gerrit-extension-api/.gitignore b/gerrit-extension-api/.gitignore new file mode 100644 index 0000000000..4e1ec9c6c1 --- /dev/null +++ b/gerrit-extension-api/.gitignore @@ -0,0 +1,6 @@ +/target +/.classpath +/.project +/.settings/org.maven.ide.eclipse.prefs +/.settings/org.eclipse.m2e.core.prefs +/gerrit-extension-api.iml diff --git a/gerrit-extension-api/.settings/org.eclipse.core.resources.prefs b/gerrit-extension-api/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000000..fc11c3fe6f --- /dev/null +++ b/gerrit-extension-api/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,5 @@ +#Thu Jul 28 11:02:36 PDT 2011 +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/test/java=UTF-8 +encoding/=UTF-8 diff --git a/gerrit-extension-api/.settings/org.eclipse.core.runtime.prefs b/gerrit-extension-api/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 0000000000..8667cfd4a3 --- /dev/null +++ b/gerrit-extension-api/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,3 @@ +#Tue Sep 02 16:59:24 PDT 2008 +eclipse.preferences.version=1 +line.separator=\n diff --git a/gerrit-extension-api/.settings/org.eclipse.jdt.core.prefs b/gerrit-extension-api/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..470942d4f6 --- /dev/null +++ b/gerrit-extension-api/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,269 @@ +#Thu Jul 28 11:02:36 PDT 2011 +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.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.6 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=16 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=0 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=0 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=true +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert +org.eclipse.jdt.core.formatter.comment.line_length=80 +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.lineSplit=80 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3 +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false +org.eclipse.jdt.core.formatter.tabulation.char=space +org.eclipse.jdt.core.formatter.tabulation.size=2 +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true diff --git a/gerrit-extension-api/.settings/org.eclipse.jdt.ui.prefs b/gerrit-extension-api/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000000..d4218a5fc0 --- /dev/null +++ b/gerrit-extension-api/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,61 @@ +#Wed Jul 29 11:31:38 PDT 2009 +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_Google Format +formatter_settings_version=11 +org.eclipse.jdt.ui.ignorelowercasenames=true +org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax; +org.eclipse.jdt.ui.ondemandthreshold=99 +org.eclipse.jdt.ui.staticondemandthreshold=99 +org.eclipse.jdt.ui.text.custom_code_templates= +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=false +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.make_local_variable_final=true +sp_cleanup.make_parameters_final=true +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=false +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/gerrit-extension-api/pom.xml b/gerrit-extension-api/pom.xml new file mode 100644 index 0000000000..0209f3fe67 --- /dev/null +++ b/gerrit-extension-api/pom.xml @@ -0,0 +1,71 @@ + + + + 4.0.0 + + + com.google.gerrit + gerrit-parent + 2.5-SNAPSHOT + + + gerrit-extension-api + Gerrit Code Review - Extension API + + + Interfaces describing the extension API + + + + + com.google.inject + guice + + + + com.google.inject.extensions + guice-servlet + + + + org.apache.tomcat + servlet-api + + + + + + + org.apache.maven.plugins + maven-shade-plugin + + true + + + + package + + shade + + + + + + + diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Export.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Export.java new file mode 100644 index 0000000000..4811e40781 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Export.java @@ -0,0 +1,52 @@ +// Copyright (C) 2012 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.extensions.annotations; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.inject.BindingAnnotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Annotation applied to auto-registered, exported types. + *

+ * Plugins or extensions using auto-registration should apply this annotation to + * any non-abstract class they want exported for access. + *

+ * For SSH commands the @Export annotation names the subcommand: + * + *

+ *   @Export("print")
+ *   class MyCommand extends SshCommand {
+ * 
+ * + * For HTTP servlets, the @Export annotation names the URL the servlet is bound + * to, relative to the plugin or extension's namespace within the Gerrit + * container. + * + *
+ *  @Export("/index.html")
+ *  class ShowIndexHtml extends HttpServlet {
+ * 
+ */ +@Target({ElementType.TYPE}) +@Retention(RUNTIME) +@BindingAnnotation +public @interface Export { + String value(); +} diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/ExportImpl.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/ExportImpl.java new file mode 100644 index 0000000000..a3e72bccc3 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/ExportImpl.java @@ -0,0 +1,52 @@ +// Copyright (C) 2012 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.extensions.annotations; + +import java.io.Serializable; +import java.lang.annotation.Annotation; + +final class ExportImpl implements Export, Serializable { + private static final long serialVersionUID = 0; + private final String value; + + ExportImpl(String value) { + this.value = value; + } + + @Override + public Class annotationType() { + return Export.class; + } + + @Override + public String value() { + return value; + } + + @Override + public int hashCode() { + return (127 * "value".hashCode()) ^ value.hashCode(); + } + + @Override + public boolean equals(Object o) { + return o instanceof Export && value.equals(((Export) o).value()); + } + + @Override + public String toString() { + return "@" + Export.class.getName() + "(value=" + value + ")"; + } +} diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Exports.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Exports.java new file mode 100644 index 0000000000..c48bcfb9c0 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Exports.java @@ -0,0 +1,26 @@ +// Copyright (C) 2012 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.extensions.annotations; + +/** Static constructors for {@link Export} annotations. */ +public final class Exports { + /** Create an annotation to export under a specific name. */ + public static Export named(String name) { + return new ExportImpl(name); + } + + private Exports() { + } +} diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/ExtensionPoint.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/ExtensionPoint.java new file mode 100644 index 0000000000..4799f5e1ff --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/ExtensionPoint.java @@ -0,0 +1,41 @@ +// Copyright (C) 2012 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.extensions.annotations; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.gerrit.extensions.registration.DynamicSet; +import com.google.inject.BindingAnnotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Annotation for interfaces that accept auto-registered implementations. + *

+ * Interfaces that accept automatically registered implementations into their + * {@link DynamicSet} must be tagged with this annotation. + *

+ * Plugins or extensions that implement an {@code @ExtensionPoint} interface + * should use the {@link Listen} annotation to automatically register. + * + * @see Listen + */ +@Target({ElementType.TYPE}) +@Retention(RUNTIME) +@BindingAnnotation +public @interface ExtensionPoint { +} diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Listen.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Listen.java new file mode 100644 index 0000000000..e4ba9316c4 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/Listen.java @@ -0,0 +1,39 @@ +// Copyright (C) 2012 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.extensions.annotations; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.inject.BindingAnnotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Annotation for auto-registered extension point implementations. + *

+ * Plugins or extensions using auto-registration should apply this annotation to + * any non-abstract class that implements an unnamed extension point, such as a + * notification listener. Gerrit will automatically determine which extension + * points to apply based on the interfaces the type implements. + * + * @see Export + */ +@Target({ElementType.TYPE}) +@Retention(RUNTIME) +@BindingAnnotation +public @interface Listen { +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginName.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginName.java similarity index 71% rename from gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginName.java rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginName.java index 6a47b935c3..672bab2fec 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginName.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/annotations/PluginName.java @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.google.gerrit.server.plugins; +package com.google.gerrit.extensions.annotations; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -22,6 +22,19 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Target; +/** + * Annotation applied to a String containing the plugin or extension name. + *

+ * A plugin or extension may receive this string by Guice injection to discover + * the name that an administrator has installed the plugin or extension under: + * + *

+ *  @Inject
+ *  MyType(@PluginName String myName) {
+ *  ...
+ *  }
+ * 
+ */ @Target({ElementType.PARAMETER, ElementType.FIELD}) @Retention(RUNTIME) @BindingAnnotation diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java new file mode 100644 index 0000000000..f114afd98d --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMap.java @@ -0,0 +1,155 @@ +// Copyright (C) 2012 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.extensions.registration; + +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.Scopes; +import com.google.inject.TypeLiteral; +import com.google.inject.util.Types; + +import java.util.Collections; +import java.util.Map; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A map of members that can be modified as plugins reload. + *

+ * Maps index their members by plugin name and export name. + *

+ * DynamicMaps are always mapped as singletons in Guice, and only may contain + * singletons, as providers are resolved to an instance before the member is + * added to the map. + */ +public abstract class DynamicMap { + /** + * Declare a singleton {@code DynamicMap} with a binder. + *

+ * Maps must be defined in a Guice module before they can be bound: + * + *

+   * DynamicMap.mapOf(binder(), Interface.class);
+   * bind(Interface.class)
+   *   .annotatedWith(Exports.named("foo"))
+   *   .to(Impl.class);
+   * 
+ * + * @param binder a new binder created in the module. + * @param member type of value in the map. + */ + public static void mapOf(Binder binder, Class member) { + mapOf(binder, TypeLiteral.get(member)); + } + + /** + * Declare a singleton {@code DynamicMap} with a binder. + *

+ * Maps must be defined in a Guice module before they can be bound: + * + *

+   * DynamicMap.mapOf(binder(), new TypeLiteral>(){});
+   * bind(new TypeLiteral>() {})
+   *   .annotatedWith(Exports.named("foo"))
+   *   .to(Impl.class);
+   * 
+ * + * @param binder a new binder created in the module. + * @param member type of value in the map. + */ + public static void mapOf(Binder binder, TypeLiteral member) { + @SuppressWarnings("unchecked") + Key> key = (Key>) Key.get( + Types.newParameterizedType(DynamicMap.class, member.getType())); + binder.bind(key) + .toProvider(new DynamicMapProvider(member)) + .in(Scopes.SINGLETON); + } + + final ConcurrentMap items; + + DynamicMap() { + items = new ConcurrentHashMap(16, 0.75f, 1); + } + + /** + * Lookup an implementation by name. + * + * @param pluginName local name of the plugin providing the item. + * @param exportName name the plugin exports the item as. + * @return the implementation. Null if the plugin is not running, or if the + * plugin does not export this name. + */ + public T get(String pluginName, String exportName) { + return items.get(new NamePair(pluginName, exportName)); + } + + /** + * Get the names of all running plugins supplying this type. + * + * @return sorted set of active plugins that supply at least one item. + */ + public SortedSet plugins() { + SortedSet r = new TreeSet(); + for (NamePair p : items.keySet()) { + r.add(p.pluginName); + } + return Collections.unmodifiableSortedSet(r); + } + + /** + * Get the items exported by a single plugin. + * + * @param pluginName name of the plugin. + * @return items exported by a plugin, keyed by the export name. + */ + public SortedMap byPlugin(String pluginName) { + SortedMap r = new TreeMap(); + for (Map.Entry e : items.entrySet()) { + if (e.getKey().pluginName.equals(pluginName)) { + r.put(e.getKey().exportName, e.getValue()); + } + } + return Collections.unmodifiableSortedMap(r); + } + + static class NamePair { + private final String pluginName; + private final String exportName; + + NamePair(String pn, String en) { + this.pluginName = pn; + this.exportName = en; + } + + @Override + public int hashCode() { + return pluginName.hashCode() * 31 + exportName.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other instanceof NamePair) { + NamePair np = (NamePair) other; + return pluginName.equals(np) && exportName.equals(np); + } + return false; + } + } +} diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java new file mode 100644 index 0000000000..d771d135e9 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicMapProvider.java @@ -0,0 +1,46 @@ +// Copyright (C) 2012 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.extensions.registration; + +import com.google.inject.Binding; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; + +import java.util.List; + +class DynamicMapProvider implements Provider> { + private final TypeLiteral type; + + @Inject + private Injector injector; + + DynamicMapProvider(TypeLiteral type) { + this.type = type; + } + + public DynamicMap get() { + PrivateInternals_DynamicMapImpl m = + new PrivateInternals_DynamicMapImpl(); + List> bindings = injector.findBindingsByType(type); + if (bindings != null) { + for (Binding b : bindings) { + m.put("gerrit", b.getKey(), b.getProvider().get()); + } + } + return m; + } +} diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java new file mode 100644 index 0000000000..7f46ad42cc --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSet.java @@ -0,0 +1,231 @@ +// Copyright (C) 2012 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.extensions.registration; + +import com.google.inject.Binder; +import com.google.inject.Key; +import com.google.inject.Scopes; +import com.google.inject.TypeLiteral; +import com.google.inject.binder.LinkedBindingBuilder; +import com.google.inject.internal.UniqueAnnotations; +import com.google.inject.name.Named; +import com.google.inject.util.Types; + +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A set of members that can be modified as plugins reload. + *

+ * DynamicSets are always mapped as singletons in Guice, and only may contain + * singletons, as providers are resolved to an instance before the member is + * added to the set. + */ +public class DynamicSet implements Iterable { + /** + * Declare a singleton {@code DynamicSet} with a binder. + *

+ * Sets must be defined in a Guice module before they can be bound: + *

+   *   DynamicSet.setOf(binder(), Interface.class);
+   *   DynamicSet.bind(binder(), Interface.class).to(Impl.class);
+   * 
+ * + * @param binder a new binder created in the module. + * @param member type of entry in the set. + */ + public static void setOf(Binder binder, Class member) { + setOf(binder, TypeLiteral.get(member)); + } + + /** + * Declare a singleton {@code DynamicSet} with a binder. + *

+ * Sets must be defined in a Guice module before they can be bound: + *

+   *   DynamicSet.setOf(binder(), new TypeLiteral>() {});
+   * 
+ * + * @param binder a new binder created in the module. + * @param member type of entry in the set. + */ + public static void setOf(Binder binder, TypeLiteral member) { + @SuppressWarnings("unchecked") + Key> key = (Key>) Key.get( + Types.newParameterizedType(DynamicSet.class, member.getType())); + binder.bind(key) + .toProvider(new DynamicSetProvider(member)) + .in(Scopes.SINGLETON); + } + + /** + * Bind one implementation into the set using a unique annotation. + * + * @param binder a new binder created in the module. + * @param type type of entries in the set. + * @return a binder to continue configuring the new set member. + */ + public static LinkedBindingBuilder bind(Binder binder, Class type) { + return bind(binder, TypeLiteral.get(type)); + } + + /** + * Bind one implementation into the set using a unique annotation. + * + * @param binder a new binder created in the module. + * @param type type of entries in the set. + * @return a binder to continue configuring the new set member. + */ + public static LinkedBindingBuilder bind(Binder binder, TypeLiteral type) { + return binder.bind(type).annotatedWith(UniqueAnnotations.create()); + } + + /** + * Bind a named implementation into the set. + * + * @param binder a new binder created in the module. + * @param type type of entries in the set. + * @param name {@code @Named} annotation to apply instead of a unique + * annotation. + * @return a binder to continue configuring the new set member. + */ + public static LinkedBindingBuilder bind(Binder binder, + Class type, + Named name) { + return bind(binder, TypeLiteral.get(type)); + } + + /** + * Bind a named implementation into the set. + * + * @param binder a new binder created in the module. + * @param type type of entries in the set. + * @param name {@code @Named} annotation to apply instead of a unique + * annotation. + * @return a binder to continue configuring the new set member. + */ + public static LinkedBindingBuilder bind(Binder binder, + TypeLiteral type, + Named name) { + return binder.bind(type).annotatedWith(name); + } + + private final CopyOnWriteArrayList> items; + + DynamicSet(Collection> base) { + items = new CopyOnWriteArrayList>(base); + } + + @Override + public Iterator iterator() { + final Iterator> itr = items.iterator(); + return new Iterator() { + private T next; + + @Override + public boolean hasNext() { + while (next == null && itr.hasNext()) { + next = itr.next().get(); + } + return next != null; + } + + @Override + public T next() { + if (hasNext()) { + T result = next; + next = null; + return result; + } + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Add one new element to the set. + * + * @param item the item to add to the collection. Must not be null. + * @return handle to remove the item at a later point in time. + */ + public RegistrationHandle add(final T item) { + final AtomicReference ref = new AtomicReference(item); + items.add(ref); + return new RegistrationHandle() { + @Override + public void remove() { + if (ref.compareAndSet(item, null)) { + items.remove(ref); + } + } + }; + } + + /** + * Add one new element that may be hot-replaceable in the future. + * + * @param key unique description from the item's Guice binding. This can be + * later obtained from the registration handle to facilitate matching + * with the new equivalent instance during a hot reload. + * @param item the item to add to the collection right now. Must not be null. + * @return a handle that can remove this item later, or hot-swap the item + * without it ever leaving the collection. + */ + public ReloadableRegistrationHandle add(Key key, T item) { + AtomicReference ref = new AtomicReference(item); + items.add(ref); + return new ReloadableHandle(ref, key, item); + } + + private class ReloadableHandle implements ReloadableRegistrationHandle { + private final AtomicReference ref; + private final Key key; + private final T item; + + ReloadableHandle(AtomicReference ref, Key key, T item) { + this.ref = ref; + this.key = key; + this.item = item; + } + + @Override + public void remove() { + if (ref.compareAndSet(item, null)) { + items.remove(ref); + } + } + + @Override + public Key getKey() { + return key; + } + + @Override + public ReloadableHandle replace(Key newKey, T newItem) { + if (ref.compareAndSet(item, newItem)) { + return new ReloadableHandle(ref, newKey, newItem); + } + return null; + } + } +} diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java new file mode 100644 index 0000000000..694fbd8e66 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/DynamicSetProvider.java @@ -0,0 +1,56 @@ +// Copyright (C) 2012 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.extensions.registration; + +import com.google.inject.Binding; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +class DynamicSetProvider implements Provider> { + private final TypeLiteral type; + + @Inject + private Injector injector; + + DynamicSetProvider(TypeLiteral type) { + this.type = type; + } + + public DynamicSet get() { + return new DynamicSet(find(injector, type)); + } + + private static List> find( + Injector src, + TypeLiteral type) { + List> bindings = src.findBindingsByType(type); + int cnt = bindings != null ? bindings.size() : 0; + if (cnt == 0) { + return Collections.emptyList(); + } + List> r = new ArrayList>(cnt); + for (Binding b : bindings) { + r.add(new AtomicReference(b.getProvider().get())); + } + return r; + } +} diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java new file mode 100644 index 0000000000..0ce4014535 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java @@ -0,0 +1,96 @@ +// Copyright (C) 2012 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.extensions.registration; + +import com.google.gerrit.extensions.annotations.Export; +import com.google.inject.Key; + +/** DO NOT USE */ +public class PrivateInternals_DynamicMapImpl extends DynamicMap { + PrivateInternals_DynamicMapImpl() { + } + + /** + * Store one new element into the map. + * + * @param pluginName unique name of the plugin providing the export. + * @param exportName name the plugin has exported the item as. + * @param item the item to add to the collection. Must not be null. + * @return handle to remove the item at a later point in time. + */ + public RegistrationHandle put( + String pluginName, String exportName, + final T item) { + final NamePair key = new NamePair(pluginName, exportName); + items.put(key, item); + return new RegistrationHandle() { + @Override + public void remove() { + items.remove(key, item); + } + }; + } + + /** + * Store one new element that may be hot-replaceable in the future. + * + * @param pluginName unique name of the plugin providing the export. + * @param key unique description from the item's Guice binding. This can be + * later obtained from the registration handle to facilitate matching + * with the new equivalent instance during a hot reload. The key must + * use an {@link @Export} annotation. + * @param item the item to add to the collection right now. Must not be null. + * @return a handle that can remove this item later, or hot-swap the item + * without it ever leaving the collection. + */ + public ReloadableRegistrationHandle put( + String pluginName, Key key, + T item) { + String exportName = ((Export) key.getAnnotation()).value(); + NamePair np = new NamePair(pluginName, exportName); + items.put(np, item); + return new ReloadableHandle(np, key, item); + } + + private class ReloadableHandle implements ReloadableRegistrationHandle { + private final NamePair np; + private final Key key; + private final T item; + + ReloadableHandle(NamePair np, Key key, T item) { + this.np = np; + this.key = key; + this.item = item; + } + + @Override + public void remove() { + items.remove(np, item); + } + + @Override + public Key getKey() { + return key; + } + + @Override + public ReloadableHandle replace(Key newKey, T newItem) { + if (items.replace(np, item, newItem)) { + return new ReloadableHandle(np, newKey, newItem); + } + return null; + } + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/RegistrationHandle.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/RegistrationHandle.java similarity index 93% rename from gerrit-server/src/main/java/com/google/gerrit/server/plugins/RegistrationHandle.java rename to gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/RegistrationHandle.java index cd0b334275..2243786249 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/RegistrationHandle.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/RegistrationHandle.java @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.google.gerrit.server.plugins; +package com.google.gerrit.extensions.registration; /** Handle for registered information. */ public interface RegistrationHandle { diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/ReloadableRegistrationHandle.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/ReloadableRegistrationHandle.java new file mode 100644 index 0000000000..b7d78c9f15 --- /dev/null +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/registration/ReloadableRegistrationHandle.java @@ -0,0 +1,23 @@ +// Copyright (C) 2012 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.extensions.registration; + +import com.google.inject.Key; + +public interface ReloadableRegistrationHandle extends RegistrationHandle { + public Key getKey(); + + public RegistrationHandle replace(Key key, T item); +} diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java new file mode 100644 index 0000000000..2d957f275f --- /dev/null +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java @@ -0,0 +1,69 @@ +// Copyright (C) 2012 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.plugins; + +import com.google.common.collect.Maps; +import com.google.gerrit.extensions.annotations.Export; +import com.google.gerrit.server.plugins.InvalidPluginException; +import com.google.gerrit.server.plugins.ModuleGenerator; +import com.google.inject.Module; +import com.google.inject.Scopes; +import com.google.inject.servlet.ServletModule; + +import java.util.Map; + +import javax.servlet.http.HttpServlet; + +class HttpAutoRegisterModuleGenerator extends ServletModule + implements ModuleGenerator { + private final Map> serve = Maps.newHashMap(); + + @Override + protected void configureServlets() { + for (Map.Entry> e : serve.entrySet()) { + bind(e.getValue()).in(Scopes.SINGLETON); + serve(e.getKey()).with(e.getValue()); + } + } + + @Override + public void setPluginName(String name) { + } + + @SuppressWarnings("unchecked") + @Override + public void export(Export export, Class type) + throws InvalidPluginException { + if (HttpServlet.class.isAssignableFrom(type)) { + Class old = serve.get(export.value()); + if (old != null) { + throw new InvalidPluginException(String.format( + "@Export(\"%s\") has duplicate bindings:\n %s\n %s", + export.value(), old.getName(), type.getName())); + } + serve.put(export.value(), (Class) type); + } else { + throw new InvalidPluginException(String.format( + "Class %s with @Export(\"%s\") must extend %s", + type.getName(), export.value(), + HttpServlet.class.getName())); + } + } + + @Override + public Module create() throws InvalidPluginException { + return this; + } +} diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java index 0ad90c2cda..2e5001b535 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginModule.java @@ -14,6 +14,7 @@ package com.google.gerrit.httpd.plugins; +import com.google.gerrit.server.plugins.ModuleGenerator; import com.google.gerrit.server.plugins.ReloadPluginListener; import com.google.gerrit.server.plugins.StartPluginListener; import com.google.inject.internal.UniqueAnnotations; @@ -32,5 +33,8 @@ public class HttpPluginModule extends ServletModule { bind(ReloadPluginListener.class) .annotatedWith(UniqueAnnotations.create()) .to(HttpPluginServlet.class); + + bind(ModuleGenerator.class) + .to(HttpAutoRegisterModuleGenerator.class); } } diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java index 86f886c19f..23dbaac0e1 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java @@ -17,9 +17,9 @@ package com.google.gerrit.httpd.plugins; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.gerrit.extensions.registration.RegistrationHandle; import com.google.gerrit.server.MimeUtilFileTypeRegistry; import com.google.gerrit.server.plugins.Plugin; -import com.google.gerrit.server.plugins.RegistrationHandle; import com.google.gerrit.server.plugins.ReloadPluginListener; import com.google.gerrit.server.plugins.StartPluginListener; import com.google.inject.Inject; diff --git a/gerrit-server/pom.xml b/gerrit-server/pom.xml index f35608c25b..70397d876a 100644 --- a/gerrit-server/pom.xml +++ b/gerrit-server/pom.xml @@ -121,6 +121,12 @@ limitations under the License. ${project.version} + + com.google.gerrit + gerrit-extension-api + ${project.version} + + com.google.gerrit gerrit-util-cli diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java new file mode 100644 index 0000000000..5aee9bfbd6 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java @@ -0,0 +1,392 @@ +// Copyright (C) 2012 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.plugins; + +import static com.google.gerrit.server.plugins.PluginGuiceEnvironment.is; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.gerrit.extensions.annotations.Export; +import com.google.gerrit.extensions.annotations.ExtensionPoint; +import com.google.gerrit.extensions.annotations.Listen; +import com.google.inject.AbstractModule; +import com.google.inject.Module; +import com.google.inject.Scopes; +import com.google.inject.TypeLiteral; +import com.google.inject.internal.UniqueAnnotations; + +import org.eclipse.jgit.util.IO; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.util.Enumeration; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +class AutoRegisterModules { + private static final int SKIP_ALL = ClassReader.SKIP_CODE + | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES; + private final String pluginName; + private final PluginGuiceEnvironment env; + private final JarFile jarFile; + private final ClassLoader classLoader; + private final ModuleGenerator sshGen; + private final ModuleGenerator httpGen; + + private Set> sysSingletons; + private Map, Class> sysListen; + + Module sysModule; + Module sshModule; + Module httpModule; + + AutoRegisterModules(String pluginName, + PluginGuiceEnvironment env, + JarFile jarFile, + ClassLoader classLoader) { + this.pluginName = pluginName; + this.env = env; + this.jarFile = jarFile; + this.classLoader = classLoader; + this.sshGen = env.hasSshModule() ? env.newSshModuleGenerator() : null; + this.httpGen = env.hasHttpModule() ? env.newHttpModuleGenerator() : null; + } + + AutoRegisterModules discover() throws InvalidPluginException { + sysSingletons = Sets.newHashSet(); + sysListen = Maps.newHashMap(); + + if (sshGen != null) { + sshGen.setPluginName(pluginName); + } + if (httpGen != null) { + httpGen.setPluginName(pluginName); + } + + scan(); + + if (!sysSingletons.isEmpty() || !sysListen.isEmpty()) { + sysModule = makeSystemModule(); + } + if (sshGen != null) { + sshModule = sshGen.create(); + } + if (httpGen != null) { + httpModule = httpGen.create(); + } + return this; + } + + private Module makeSystemModule() { + return new AbstractModule() { + @Override + protected void configure() { + for (Class clazz : sysSingletons) { + bind(clazz).in(Scopes.SINGLETON); + } + for (Map.Entry, Class> e : sysListen.entrySet()) { + @SuppressWarnings("unchecked") + TypeLiteral type = (TypeLiteral) e.getKey(); + + @SuppressWarnings("unchecked") + Class impl = (Class) e.getValue(); + + Annotation n = impl.getAnnotation(Export.class); + if (n == null) { + n = impl.getAnnotation(javax.inject.Named.class); + } + if (n == null) { + n = impl.getAnnotation(com.google.inject.name.Named.class); + } + if (n == null) { + n = UniqueAnnotations.create(); + } + bind(type).annotatedWith(n).to(impl); + } + } + }; + } + + private void scan() throws InvalidPluginException { + Enumeration e = jarFile.entries(); + while (e.hasMoreElements()) { + JarEntry entry = e.nextElement(); + if (skip(entry)) { + continue; + } + + ClassData def = new ClassData(); + try { + new ClassReader(read(entry)).accept(def, SKIP_ALL); + } catch (IOException err) { + throw new InvalidPluginException("Cannot auto-register", err); + } catch (RuntimeException err) { + PluginLoader.log.warn(String.format( + "Plugin %s has invaild class file %s inside of %s", + pluginName, entry.getName(), jarFile.getName()), err); + continue; + } + + if (def.exportedAsName != null) { + if (def.isConcrete()) { + export(def); + } else { + PluginLoader.log.warn(String.format( + "Plugin %s tries to @Export(\"%s\") abstract class %s", + pluginName, def.exportedAsName, def.className)); + } + } else if (def.listen) { + if (def.isConcrete()) { + listen(def); + } else { + PluginLoader.log.warn(String.format( + "Plugin %s tries to @Listen abstract class %s", + pluginName, def.className)); + } + } + } + } + + private void export(ClassData def) throws InvalidPluginException { + Class clazz; + try { + clazz = Class.forName(def.className, false, classLoader); + } catch (ClassNotFoundException err) { + throw new InvalidPluginException(String.format( + "Cannot load %s with @Export(\"%s\")", + def.className, def.exportedAsName), err); + } + + Export export = clazz.getAnnotation(Export.class); + if (export == null) { + PluginLoader.log.warn(String.format( + "In plugin %s asm incorrectly parsed %s with @Export(\"%s\")", + pluginName, clazz.getName(), def.exportedAsName)); + return; + } + + if (is("org.apache.sshd.server.Command", clazz)) { + if (sshGen != null) { + sshGen.export(export, clazz); + } + } else if (is("javax.servlet.http.HttpServlet", clazz)) { + if (httpGen != null) { + httpGen.export(export, clazz); + listen(clazz, clazz); + } + } else { + int cnt = sysListen.size(); + listen(clazz, clazz); + if (cnt == sysListen.size()) { + // If no bindings were recorded, the extension isn't recognized. + throw new InvalidPluginException(String.format( + "Class %s with @Export(\"%s\") not supported", + clazz.getName(), export.value())); + } + } + } + + private void listen(ClassData def) throws InvalidPluginException { + Class clazz; + try { + clazz = Class.forName(def.className, false, classLoader); + } catch (ClassNotFoundException err) { + throw new InvalidPluginException(String.format( + "Cannot load %s with @Listen", + def.className), err); + } + + Listen listen = clazz.getAnnotation(Listen.class); + if (listen != null) { + listen(clazz, clazz); + } else { + PluginLoader.log.warn(String.format( + "In plugin %s asm incorrectly parsed %s with @Listen", + pluginName, clazz.getName())); + } + } + + private void listen(java.lang.reflect.Type type, Class clazz) + throws InvalidPluginException { + while (type != null) { + Class rawType; + if (type instanceof ParameterizedType) { + rawType = (Class) ((ParameterizedType) type).getRawType(); + } else if (type instanceof Class) { + rawType = (Class) type; + } else { + return; + } + + if (rawType.getAnnotation(ExtensionPoint.class) != null) { + TypeLiteral tl = TypeLiteral.get(type); + if (env.hasDynamicSet(tl)) { + sysSingletons.add(clazz); + sysListen.put(tl, clazz); + } else if (env.hasDynamicMap(tl)) { + if (clazz.getAnnotation(Export.class) == null) { + throw new InvalidPluginException(String.format( + "Class %s requires @Export(\"name\") annotation for %s", + clazz.getName(), rawType.getName())); + } + sysSingletons.add(clazz); + sysListen.put(tl, clazz); + } else { + throw new InvalidPluginException(String.format( + "Cannot register %s, server does not accept %s", + clazz.getName(), rawType.getName())); + } + return; + } + + java.lang.reflect.Type[] interfaces = rawType.getGenericInterfaces(); + if (interfaces != null) { + for (java.lang.reflect.Type i : interfaces) { + listen(i, clazz); + } + } + + type = rawType.getGenericSuperclass(); + } + } + + private static boolean skip(JarEntry entry) { + if (!entry.getName().endsWith(".class")) { + return true; // Avoid non-class resources. + } + if (entry.getSize() <= 0) { + return true; // Directories have 0 size. + } + if (entry.getSize() >= 1024 * 1024) { + return true; // Do not scan huge class files. + } + return false; + } + + private byte[] read(JarEntry entry) throws IOException { + byte[] data = new byte[(int) entry.getSize()]; + InputStream in = jarFile.getInputStream(entry); + try { + IO.readFully(in, data, 0, data.length); + } finally { + in.close(); + } + return data; + } + + private static class ClassData implements ClassVisitor { + private static final String EXPORT = Type.getType(Export.class).getDescriptor(); + private static final String LISTEN = Type.getType(Listen.class).getDescriptor(); + + String className; + int access; + String exportedAsName; + boolean listen; + + boolean isConcrete() { + return (access & Opcodes.ACC_ABSTRACT) == 0 + && (access & Opcodes.ACC_INTERFACE) == 0; + } + + @Override + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + this.className = Type.getObjectType(name).getClassName(); + this.access = access; + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (visible && EXPORT.equals(desc)) { + return new AbstractAnnotationVisitor() { + @Override + public void visit(String name, Object value) { + exportedAsName = (String) value; + } + }; + } + if (visible && LISTEN.equals(desc)) { + listen = true; + return null; + } + return null; + } + + @Override + public void visitSource(String arg0, String arg1) { + } + + @Override + public void visitOuterClass(String arg0, String arg1, String arg2) { + } + + @Override + public MethodVisitor visitMethod(int arg0, String arg1, String arg2, + String arg3, String[] arg4) { + return null; + } + + @Override + public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) { + } + + @Override + public FieldVisitor visitField(int arg0, String arg1, String arg2, + String arg3, Object arg4) { + return null; + } + + @Override + public void visitEnd() { + } + + @Override + public void visitAttribute(Attribute arg0) { + } + } + + private static abstract class AbstractAnnotationVisitor implements + AnnotationVisitor { + @Override + public AnnotationVisitor visitAnnotation(String arg0, String arg1) { + return null; + } + + @Override + public AnnotationVisitor visitArray(String arg0) { + return null; + } + + @Override + public void visitEnum(String arg0, String arg1, String arg2) { + } + + @Override + public void visitEnd() { + } + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InvalidPluginException.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InvalidPluginException.java new file mode 100644 index 0000000000..31be10cab6 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InvalidPluginException.java @@ -0,0 +1,27 @@ +// Copyright (C) 2012 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.plugins; + +public class InvalidPluginException extends Exception { + private static final long serialVersionUID = 1L; + + public InvalidPluginException(String message) { + super(message); + } + + public InvalidPluginException(String message, Throwable why) { + super(message, why); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ModuleGenerator.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ModuleGenerator.java new file mode 100644 index 0000000000..92e3b1dd6b --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ModuleGenerator.java @@ -0,0 +1,26 @@ +// Copyright (C) 2012 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.plugins; + +import com.google.gerrit.extensions.annotations.Export; +import com.google.inject.Module; + +public interface ModuleGenerator { + void setPluginName(String name); + + void export(Export export, Class type) throws InvalidPluginException; + + Module create() throws InvalidPluginException; +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java index 9e8da32e4b..c47f370494 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java @@ -15,17 +15,22 @@ package com.google.gerrit.server.plugins; import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.gerrit.extensions.annotations.PluginName; +import com.google.gerrit.extensions.registration.RegistrationHandle; +import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle; import com.google.gerrit.lifecycle.LifecycleListener; import com.google.gerrit.lifecycle.LifecycleManager; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; -import com.google.inject.servlet.GuiceFilter; import org.eclipse.jgit.storage.file.FileSnapshot; import java.io.File; +import java.util.Collections; +import java.util.List; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -36,7 +41,7 @@ public class Plugin { static { // Guice logs warnings about multiple injectors being created. // Silence this in case HTTP plugins are used. - java.util.logging.Logger.getLogger(GuiceFilter.class.getName()) + java.util.logging.Logger.getLogger("com.google.inject.servlet.GuiceFilter") .setLevel(java.util.logging.Level.OFF); } @@ -45,6 +50,7 @@ public class Plugin { private final FileSnapshot snapshot; private final JarFile jarFile; private final Manifest manifest; + private final ClassLoader classLoader; private Class sysModule; private Class sshModule; private Class httpModule; @@ -53,12 +59,14 @@ public class Plugin { private Injector sshInjector; private Injector httpInjector; private LifecycleManager manager; + private List> reloadableHandles; public Plugin(String name, File srcJar, FileSnapshot snapshot, JarFile jarFile, Manifest manifest, + ClassLoader classLoader, @Nullable Class sysModule, @Nullable Class sshModule, @Nullable Class httpModule) { @@ -67,6 +75,7 @@ public class Plugin { this.snapshot = snapshot; this.jarFile = jarFile; this.manifest = manifest; + this.classLoader = classLoader; this.sysModule = sysModule; this.sshModule = sshModule; this.httpModule = httpModule; @@ -108,25 +117,48 @@ public class Plugin { Injector root = newRootInjector(env); manager = new LifecycleManager(); + AutoRegisterModules auto = null; + if (sysModule == null && sshModule == null && httpModule == null) { + auto = new AutoRegisterModules(name, env, jarFile, classLoader); + auto.discover(); + } + if (sysModule != null) { sysInjector = root.createChildInjector(root.getInstance(sysModule)); manager.add(sysInjector); + } else if (auto != null && auto.sysModule != null) { + sysInjector = root.createChildInjector(auto.sysModule); + manager.add(sysInjector); } else { sysInjector = root; } - if (sshModule != null && env.hasSshModule()) { - sshInjector = sysInjector.createChildInjector( - env.getSshModule(), - sysInjector.getInstance(sshModule)); - manager.add(sshInjector); + if (env.hasSshModule()) { + if (sshModule != null) { + sshInjector = sysInjector.createChildInjector( + env.getSshModule(), + sysInjector.getInstance(sshModule)); + manager.add(sshInjector); + } else if (auto != null && auto.sshModule != null) { + sshInjector = sysInjector.createChildInjector( + env.getSshModule(), + auto.sshModule); + manager.add(sshInjector); + } } - if (httpModule != null && env.hasHttpModule()) { - httpInjector = sysInjector.createChildInjector( - env.getHttpModule(), - sysInjector.getInstance(httpModule)); - manager.add(httpInjector); + if (env.hasHttpModule()) { + if (httpModule != null) { + httpInjector = sysInjector.createChildInjector( + env.getHttpModule(), + sysInjector.getInstance(httpModule)); + manager.add(httpInjector); + } else if (auto != null && auto.httpModule != null) { + httpInjector = sysInjector.createChildInjector( + env.getHttpModule(), + auto.httpModule); + manager.add(httpInjector); + } } manager.start(); @@ -159,6 +191,10 @@ public class Plugin { return jarFile; } + public Injector getSysInjector() { + return sysInjector; + } + @Nullable public Injector getSshInjector() { return sshInjector; @@ -170,6 +206,13 @@ public class Plugin { } public void add(final RegistrationHandle handle) { + if (handle instanceof ReloadableRegistrationHandle) { + if (reloadableHandles == null) { + reloadableHandles = Lists.newArrayList(); + } + reloadableHandles.add((ReloadableRegistrationHandle) handle); + } + add(new LifecycleListener() { @Override public void start() { @@ -186,6 +229,13 @@ public class Plugin { manager.add(listener); } + List> getReloadableHandles() { + if (reloadableHandles != null) { + return reloadableHandles; + } + return Collections.emptyList(); + } + @Override public String toString() { return "Plugin [" + name + "]"; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java index 0e8a95debe..1b94c0cabe 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java @@ -14,21 +14,36 @@ package com.google.gerrit.server.plugins; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.extensions.registration.DynamicSet; +import com.google.gerrit.extensions.registration.PrivateInternals_DynamicMapImpl; +import com.google.gerrit.extensions.registration.RegistrationHandle; +import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle; import com.google.gerrit.lifecycle.LifecycleListener; import com.google.inject.AbstractModule; import com.google.inject.Binding; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; +import com.google.inject.Provider; import com.google.inject.Singleton; import com.google.inject.TypeLiteral; +import com.google.inject.internal.UniqueAnnotations; +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import javax.annotation.Nullable; import javax.inject.Inject; /** @@ -44,10 +59,22 @@ public class PluginGuiceEnvironment { private final CopyConfigModule copyConfigModule; private final List onStart; private final List onReload; + private Module sysModule; private Module sshModule; private Module httpModule; + private Provider sshGen; + private Provider httpGen; + + private Map, DynamicSet> sysSets; + private Map, DynamicSet> sshSets; + private Map, DynamicSet> httpSets; + + private Map, DynamicMap> sysMaps; + private Map, DynamicMap> sshMaps; + private Map, DynamicMap> httpMaps; + @Inject PluginGuiceEnvironment(Injector sysInjector, CopyConfigModule ccm) { this.sysInjector = sysInjector; @@ -58,6 +85,21 @@ public class PluginGuiceEnvironment { onReload = new CopyOnWriteArrayList(); onReload.addAll(listeners(sysInjector, ReloadPluginListener.class)); + + sysSets = dynamicSetsOf(sysInjector); + sysMaps = dynamicMapsOf(sysInjector); + } + + boolean hasDynamicSet(TypeLiteral type) { + return sysSets.containsKey(type) + || (sshSets != null && sshSets.containsKey(type)) + || (httpSets != null && httpSets.containsKey(type)); + } + + boolean hasDynamicMap(TypeLiteral type) { + return sysMaps.containsKey(type) + || (sshMaps != null && sshMaps.containsKey(type)) + || (httpMaps != null && httpMaps.containsKey(type)); } Module getSysModule() { @@ -79,6 +121,9 @@ public class PluginGuiceEnvironment { public void setSshInjector(Injector injector) { sshModule = copy(injector); + sshGen = injector.getProvider(ModuleGenerator.class); + sshSets = dynamicSetsOf(injector); + sshMaps = dynamicMapsOf(injector); onStart.addAll(listeners(injector, StartPluginListener.class)); onReload.addAll(listeners(injector, ReloadPluginListener.class)); } @@ -91,8 +136,15 @@ public class PluginGuiceEnvironment { return sshModule; } + ModuleGenerator newSshModuleGenerator() { + return sshGen.get(); + } + public void setHttpInjector(Injector injector) { httpModule = copy(injector); + httpGen = injector.getProvider(ModuleGenerator.class); + httpSets = dynamicSetsOf(injector); + httpMaps = dynamicMapsOf(injector); onStart.addAll(listeners(injector, StartPluginListener.class)); onReload.addAll(listeners(injector, ReloadPluginListener.class)); } @@ -105,31 +157,265 @@ public class PluginGuiceEnvironment { return httpModule; } + ModuleGenerator newHttpModuleGenerator() { + return httpGen.get(); + } + void onStartPlugin(Plugin plugin) { for (StartPluginListener l : onStart) { l.onStartPlugin(plugin); } + + attachSet(sysSets, plugin.getSysInjector(), plugin); + attachSet(sshSets, plugin.getSshInjector(), plugin); + attachSet(httpSets, plugin.getHttpInjector(), plugin); + + attachMap(sysMaps, plugin.getSysInjector(), plugin); + attachMap(sshMaps, plugin.getSshInjector(), plugin); + attachMap(httpMaps, plugin.getHttpInjector(), plugin); + } + + private void attachSet(Map, DynamicSet> sets, + @Nullable Injector src, + Plugin plugin) { + if (src != null && sets != null && !sets.isEmpty()) { + for (Map.Entry, DynamicSet> e : sets.entrySet()) { + @SuppressWarnings("unchecked") + TypeLiteral type = (TypeLiteral) e.getKey(); + + @SuppressWarnings("unchecked") + DynamicSet set = (DynamicSet) e.getValue(); + + for (Binding b : bindings(src, type)) { + plugin.add(set.add(b.getKey(), b.getProvider().get())); + } + } + } + } + + private void attachMap(Map, DynamicMap> maps, + @Nullable Injector src, + Plugin plugin) { + if (src != null && maps != null && !maps.isEmpty()) { + for (Map.Entry, DynamicMap> e : maps.entrySet()) { + @SuppressWarnings("unchecked") + TypeLiteral type = (TypeLiteral) e.getKey(); + + @SuppressWarnings("unchecked") + PrivateInternals_DynamicMapImpl set = + (PrivateInternals_DynamicMapImpl) e.getValue(); + + for (Binding b : bindings(src, type)) { + plugin.add(set.put( + plugin.getName(), + b.getKey(), + b.getProvider().get())); + } + } + } } void onReloadPlugin(Plugin oldPlugin, Plugin newPlugin) { for (ReloadPluginListener l : onReload) { l.onReloadPlugin(oldPlugin, newPlugin); } + + // Index all old registrations by the raw type. These may be replaced + // during the reattach calls below. Any that are not replaced will be + // removed when the old plugin does its stop routine. + ListMultimap, ReloadableRegistrationHandle> old = + LinkedListMultimap.create(); + for (ReloadableRegistrationHandle h : oldPlugin.getReloadableHandles()) { + old.put(h.getKey().getTypeLiteral(), h); + } + + reattachMap(old, sysMaps, newPlugin.getSysInjector(), newPlugin); + reattachMap(old, sshMaps, newPlugin.getSshInjector(), newPlugin); + reattachMap(old, httpMaps, newPlugin.getHttpInjector(), newPlugin); + + reattachSet(old, sysSets, newPlugin.getSysInjector(), newPlugin); + reattachSet(old, sshSets, newPlugin.getSshInjector(), newPlugin); + reattachSet(old, httpSets, newPlugin.getHttpInjector(), newPlugin); } - private static List listeners(Injector src, Class type) { - List> bindings = src.findBindingsByType(TypeLiteral.get(type)); - List found = Lists.newArrayListWithCapacity(bindings.size()); - for (Binding b : bindings) { - found.add(b.getProvider().get()); + private void reattachMap( + ListMultimap, ReloadableRegistrationHandle> oldHandles, + Map, DynamicMap> maps, + @Nullable Injector src, + Plugin newPlugin) { + if (src == null || maps == null || maps.isEmpty()) { + return; + } + + for (Map.Entry, DynamicMap> e : maps.entrySet()) { + @SuppressWarnings("unchecked") + TypeLiteral type = (TypeLiteral) e.getKey(); + + @SuppressWarnings("unchecked") + PrivateInternals_DynamicMapImpl map = + (PrivateInternals_DynamicMapImpl) e.getValue(); + + Map> am = Maps.newHashMap(); + for (ReloadableRegistrationHandle h : oldHandles.get(type)) { + Annotation a = h.getKey().getAnnotation(); + if (a != null && !UNIQUE_ANNOTATION.isInstance(a)) { + am.put(a, h); + } + } + + for (Binding binding : bindings(src, e.getKey())) { + @SuppressWarnings("unchecked") + Binding b = (Binding) binding; + Key key = b.getKey(); + + @SuppressWarnings("unchecked") + ReloadableRegistrationHandle h = + (ReloadableRegistrationHandle) am.remove(key.getAnnotation()); + if (h != null) { + replace(newPlugin, h, b); + oldHandles.remove(type, h); + } else { + newPlugin.add(map.put( + newPlugin.getName(), + b.getKey(), + b.getProvider().get())); + } + } + } + } + + /** Type used to declare unique annotations. Guice hides this, so extract it. */ + private static final Class UNIQUE_ANNOTATION = + UniqueAnnotations.create().getClass(); + + private void reattachSet( + ListMultimap, ReloadableRegistrationHandle> oldHandles, + Map, DynamicSet> sets, + @Nullable Injector src, + Plugin newPlugin) { + if (src == null || sets == null || sets.isEmpty()) { + return; + } + + for (Map.Entry, DynamicSet> e : sets.entrySet()) { + @SuppressWarnings("unchecked") + TypeLiteral type = (TypeLiteral) e.getKey(); + + @SuppressWarnings("unchecked") + DynamicSet set = (DynamicSet) e.getValue(); + + // Index all old handles that match this DynamicSet keyed by + // annotations. Ignore the unique annotations, thereby favoring + // the @Named annotations or some other non-unique naming. + Map> am = Maps.newHashMap(); + List> old = oldHandles.get(type); + Iterator> oi = old.iterator(); + while (oi.hasNext()) { + ReloadableRegistrationHandle h = oi.next(); + Annotation a = h.getKey().getAnnotation(); + if (a != null && !UNIQUE_ANNOTATION.isInstance(a)) { + am.put(a, h); + oi.remove(); + } + } + + // Replace old handles with new bindings, favoring cases where there + // is an exact match on an @Named annotation. If there is no match + // pick any handle and replace it. We generally expect only one + // handle of each DynamicSet type when using unique annotations, but + // possibly multiple ones if @Named was used. Plugin authors that want + // atomic replacement across reloads should use @Named annotations with + // stable names that do not change across plugin versions to ensure the + // handles are swapped correctly. + oi = old.iterator(); + for (Binding binding : bindings(src, type)) { + @SuppressWarnings("unchecked") + Binding b = (Binding) binding; + Key key = b.getKey(); + + @SuppressWarnings("unchecked") + ReloadableRegistrationHandle h1 = + (ReloadableRegistrationHandle) am.remove(key.getAnnotation()); + if (h1 != null) { + replace(newPlugin, h1, b); + } else if (oi.hasNext()) { + @SuppressWarnings("unchecked") + ReloadableRegistrationHandle h2 = + (ReloadableRegistrationHandle) oi.next(); + oi.remove(); + replace(newPlugin, h2, b); + } else { + newPlugin.add(set.add(b.getKey(), b.getProvider().get())); + } + } + } + } + + private static void replace(Plugin newPlugin, + ReloadableRegistrationHandle h, Binding b) { + RegistrationHandle n = h.replace(b.getKey(), b.getProvider().get()); + if (n != null){ + newPlugin.add(n); + } + } + + static List listeners(Injector src, Class type) { + List> bindings = bindings(src, TypeLiteral.get(type)); + int cnt = bindings != null ? bindings.size() : 0; + List found = Lists.newArrayListWithCapacity(cnt); + if (bindings != null) { + for (Binding b : bindings) { + found.add(b.getProvider().get()); + } } return found; } + private static List> bindings(Injector src, TypeLiteral type) { + return src.findBindingsByType(type); + } + + private static Map, DynamicSet> dynamicSetsOf(Injector src) { + Map, DynamicSet> m = Maps.newHashMap(); + for (Map.Entry, Binding> e : src.getBindings().entrySet()) { + TypeLiteral type = e.getKey().getTypeLiteral(); + if (type.getRawType() == DynamicSet.class) { + ParameterizedType p = (ParameterizedType) type.getType(); + m.put(TypeLiteral.get(p.getActualTypeArguments()[0]), + (DynamicSet) e.getValue().getProvider().get()); + } + } + return m; + } + + private static Map, DynamicMap> dynamicMapsOf(Injector src) { + Map, DynamicMap> m = Maps.newHashMap(); + for (Map.Entry, Binding> e : src.getBindings().entrySet()) { + TypeLiteral type = e.getKey().getTypeLiteral(); + if (type.getRawType() == DynamicMap.class) { + ParameterizedType p = (ParameterizedType) type.getType(); + m.put(TypeLiteral.get(p.getActualTypeArguments()[0]), + (DynamicMap) e.getValue().getProvider().get()); + } + } + return m; + } + private static Module copy(Injector src) { + Set> dynamicTypes = Sets.newHashSet(); + for (Map.Entry, Binding> e : src.getBindings().entrySet()) { + TypeLiteral type = e.getKey().getTypeLiteral(); + if (type.getRawType() == DynamicSet.class + || type.getRawType() == DynamicMap.class) { + ParameterizedType t = (ParameterizedType) type.getType(); + dynamicTypes.add(TypeLiteral.get(t.getActualTypeArguments()[0])); + } + } + final Map, Binding> bindings = Maps.newLinkedHashMap(); for (Map.Entry, Binding> e : src.getBindings().entrySet()) { - if (shouldCopy(e.getKey())) { + if (!dynamicTypes.contains(e.getKey().getTypeLiteral()) + && shouldCopy(e.getKey())) { bindings.put(e.getKey(), e.getValue()); } } @@ -202,22 +488,22 @@ public class PluginGuiceEnvironment { return true; } - private static boolean is(String name, Class type) { - Class p = type; - while (p != null) { - if (name.equals(p.getName())) { + static boolean is(String name, Class type) { + while (type != null) { + if (name.equals(type.getName())) { return true; } - p = p.getSuperclass(); - } - Class[] interfaces = type.getInterfaces(); - if (interfaces != null) { - for (Class i : interfaces) { - if (is(name, i)) { - return true; + Class[] interfaces = type.getInterfaces(); + if (interfaces != null) { + for (Class i : interfaces) { + if (is(name, i)) { + return true; + } } } + + type = type.getSuperclass(); } return false; } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java index 330dc467f7..16cd78c139 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java @@ -337,6 +337,7 @@ public class PluginLoader implements LifecycleListener { return new Plugin(name, srcJar, snapshot, jarFile, manifest, + pluginLoader, sysModule, sshModule, httpModule); } finally { if (!keep) { diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommandProvider.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommandProvider.java index 3560d99e86..d70d32fb63 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommandProvider.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/DispatchCommandProvider.java @@ -15,7 +15,7 @@ package com.google.gerrit.sshd; import com.google.common.collect.Maps; -import com.google.gerrit.server.plugins.RegistrationHandle; +import com.google.gerrit.extensions.registration.RegistrationHandle; import com.google.inject.Binding; import com.google.inject.Inject; import com.google.inject.Injector; diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java new file mode 100644 index 0000000000..b843893d66 --- /dev/null +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java @@ -0,0 +1,74 @@ +// Copyright (C) 2012 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.sshd; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import com.google.gerrit.extensions.annotations.Export; +import com.google.gerrit.server.plugins.InvalidPluginException; +import com.google.gerrit.server.plugins.ModuleGenerator; +import com.google.inject.AbstractModule; +import com.google.inject.Module; + +import org.apache.sshd.server.Command; + +import java.util.Map; + +class SshAutoRegisterModuleGenerator + extends AbstractModule + implements ModuleGenerator { + private final Map> commands = Maps.newHashMap(); + private CommandName command; + + @Override + protected void configure() { + bind(Commands.key(command)) + .toProvider(new DispatchCommandProvider(command)); + for (Map.Entry> e : commands.entrySet()) { + bind(Commands.key(command, e.getKey())).to(e.getValue()); + } + } + + public void setPluginName(String name) { + command = Commands.named(name); + } + + @SuppressWarnings("unchecked") + @Override + public void export(Export export, Class type) + throws InvalidPluginException { + Preconditions.checkState(command != null, "pluginName must be provided"); + if (Command.class.isAssignableFrom(type)) { + Class old = commands.get(export.value()); + if (old != null) { + throw new InvalidPluginException(String.format( + "@Export(\"%s\") has duplicate bindings:\n %s\n %s", + export.value(), old.getName(), type.getName())); + } + commands.put(export.value(), (Class) type); + } else { + throw new InvalidPluginException(String.format( + "Class %s with @Export(\"%s\") must extend %s or implement %s", + type.getName(), export.value(), + SshCommand.class.getName(), Command.class.getName())); + } + } + + @Override + public Module create() throws InvalidPluginException { + Preconditions.checkState(command != null, "pluginName must be provided"); + return this; + } +} diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java index cd78796668..bc094f9399 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java @@ -31,6 +31,7 @@ import com.google.gerrit.server.config.FactoryModule; import com.google.gerrit.server.config.GerritRequestModule; import com.google.gerrit.server.git.QueueProvider; import com.google.gerrit.server.git.WorkQueue; +import com.google.gerrit.server.plugins.ModuleGenerator; import com.google.gerrit.server.plugins.ReloadPluginListener; import com.google.gerrit.server.plugins.StartPluginListener; import com.google.gerrit.server.project.ProjectControl; @@ -94,6 +95,7 @@ public class SshModule extends FactoryModule { install(new LifecycleModule() { @Override protected void configure() { + bind(ModuleGenerator.class).to(SshAutoRegisterModuleGenerator.class); bind(SshPluginStarterCallback.class); bind(StartPluginListener.class) .annotatedWith(UniqueAnnotations.create()) diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginCommandModule.java index d9015c6a72..28d267cf25 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginCommandModule.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginCommandModule.java @@ -15,7 +15,7 @@ package com.google.gerrit.sshd.commands; import com.google.common.base.Preconditions; -import com.google.gerrit.server.plugins.PluginName; +import com.google.gerrit.extensions.annotations.PluginName; import com.google.gerrit.sshd.CommandName; import com.google.gerrit.sshd.Commands; import com.google.gerrit.sshd.DispatchCommandProvider; diff --git a/pom.xml b/pom.xml index 1282619908..f366c4d2b3 100644 --- a/pom.xml +++ b/pom.xml @@ -87,6 +87,7 @@ limitations under the License. gerrit-gwtdebug gerrit-war + gerrit-extension-api gerrit-plugin-api gerrit-gwtui diff --git a/tools/deploy_api.sh b/tools/deploy_api.sh new file mode 100755 index 0000000000..eda841fb51 --- /dev/null +++ b/tools/deploy_api.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +SRC=$(ls gerrit-plugin-api/target/gerrit-plugin-api-*-sources.jar) +VER=${SRC#gerrit-plugin-api/target/gerrit-plugin-api-} +VER=${VER%-sources.jar} + +type=release +case $VER in +*-SNAPSHOT) + echo >&2 "fatal: Cannot deploy $VER" + echo >&2 " Use ./tools/version.sh --release && mvn clean package" + exit 1 + ;; +*-[0-9]*-g*) type=snapshot ;; +esac +URL=s3://gerrit-api@commondatastorage.googleapis.com/$type + +echo "Deploying API $VER to $URL" +for module in gerrit-extension-api gerrit-plugin-api +do + mvn deploy:deploy-file \ + -DgroupId=com.google.gerrit \ + -DartifactId=$module \ + -Dversion=$VER \ + -Dpackaging=jar \ + -Dfile=$module/target/$module-$VER.jar \ + -DrepositoryId=gerrit-api-repository \ + -Durl=$URL + + mvn deploy:deploy-file \ + -DgroupId=com.google.gerrit \ + -DartifactId=$module \ + -Dversion=$VER \ + -Dpackaging=java-source \ + -Dfile=$module/target/$module-$VER-sources.jar \ + -Djava-source=false \ + -DrepositoryId=gerrit-api-repository \ + -Durl=$URL +done diff --git a/tools/deploy_plugin_api.sh b/tools/deploy_plugin_api.sh deleted file mode 100755 index fe19177c40..0000000000 --- a/tools/deploy_plugin_api.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/sh - -SRC=$(ls gerrit-plugin-api/target/gerrit-plugin-api-*-sources.jar) -VER=${SRC#gerrit-plugin-api/target/gerrit-plugin-api-} -VER=${VER%-sources.jar} -JAR=gerrit-plugin-api/target/gerrit-plugin-api-$VER.jar - -type=release -case $VER in -*-SNAPSHOT) - echo >&2 "fatal: Cannot deploy $VER" - echo >&2 " Use ./tools/version.sh --release && mvn clean package" - exit 1 - ;; -*-[0-9]*-g*) type=snapshot ;; -esac -URL=s3://gerrit-api@commondatastorage.googleapis.com/$type - -echo "Deploying gerrit-plugin-api $VER to $URL" -mvn deploy:deploy-file \ - -DgroupId=com.google.gerrit \ - -DartifactId=gerrit-plugin-api \ - -Dversion=$VER \ - -Dpackaging=jar \ - -Dfile=$JAR \ - -DrepositoryId=gerrit-api-repository \ - -Durl=$URL - -mvn deploy:deploy-file \ - -DgroupId=com.google.gerrit \ - -DartifactId=gerrit-plugin-api \ - -Dversion=$VER \ - -Dpackaging=java-source \ - -Dfile=$SRC \ - -Djava-source=false \ - -DrepositoryId=gerrit-api-repository \ - -Durl=$URL