From cdcb41d7bd04babdb289b9f4ecc5b1d3e09e0bdc Mon Sep 17 00:00:00 2001 From: Bas Passon Date: Wed, 27 Jan 2021 21:18:43 +0100 Subject: [PATCH 1/8] Initial version of the client --- .editorconfig | 332 ++++++++++++++++ LICENSE | 2 +- README.md | 79 +++- deployment/pom.xml | 64 +++ .../QuarkusRabbitmqClientProcessor.java | 36 ++ .../rabbitmqclient/DummyServer.java | 68 ++++ ...uarkusRabbitmqMinimalClientConfigTest.java | 34 ++ ...kusRabbitmqNonDefaultClientConfigTest.java | 36 ++ .../QuarkusRabbitmqReadyCheckTest.java | 58 +++ .../rabbitmqclient/RabbitMQConfigTest.java | 107 +++++ .../resources/minimal-properties.properties | 10 + .../non-default-properties.properties | 53 +++ .../ready-check-properties.properties | 12 + docs/modules/ROOT/pages/config.adoc | 112 ------ docs/modules/ROOT/pages/index.adoc | 85 +++- .../ROOT/pages/quarkus-rabbitmqclient.adoc | 376 ++++++++++++++++++ pom.xml | 57 +++ runtime/pom.xml | 71 ++++ .../rabbitmqclient/RabbitMQClient.java | 35 ++ .../RabbitMQClientBuildConfig.java | 15 + .../rabbitmqclient/RabbitMQClientConfig.java | 280 +++++++++++++ .../RabbitMQClientException.java | 35 ++ .../rabbitmqclient/RabbitMQClientImpl.java | 74 ++++ .../RabbitMQClientProducer.java | 44 ++ .../rabbitmqclient/RabbitMQHelper.java | 174 ++++++++ .../rabbitmqclient/RabbitMQReadyCheck.java | 48 +++ 26 files changed, 2177 insertions(+), 120 deletions(-) create mode 100644 .editorconfig create mode 100644 deployment/pom.xml create mode 100644 deployment/src/main/java/io/quarkiverse/rabbitmqclient/deployment/QuarkusRabbitmqClientProcessor.java create mode 100644 deployment/src/test/java/io/quarkiverse/rabbitmqclient/DummyServer.java create mode 100644 deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqMinimalClientConfigTest.java create mode 100644 deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqNonDefaultClientConfigTest.java create mode 100644 deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqReadyCheckTest.java create mode 100644 deployment/src/test/java/io/quarkiverse/rabbitmqclient/RabbitMQConfigTest.java create mode 100644 deployment/src/test/resources/minimal-properties.properties create mode 100644 deployment/src/test/resources/non-default-properties.properties create mode 100644 deployment/src/test/resources/ready-check-properties.properties delete mode 100644 docs/modules/ROOT/pages/config.adoc create mode 100644 docs/modules/ROOT/pages/quarkus-rabbitmqclient.adoc create mode 100644 pom.xml create mode 100644 runtime/pom.xml create mode 100644 runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClient.java create mode 100644 runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientBuildConfig.java create mode 100644 runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientConfig.java create mode 100644 runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientException.java create mode 100644 runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientImpl.java create mode 100644 runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientProducer.java create mode 100644 runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQHelper.java create mode 100644 runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQReadyCheck.java diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e851344 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,332 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 120 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = false +ij_smart_tabs = false +ij_visual_guides = none +ij_wrap_on_typing = false + +[*.java] +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = true +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = true +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = true +ij_java_align_multiline_resources = true +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = off +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = off +ij_java_assignment_wrap = off +ij_java_binary_operation_sign_on_next_line = false +ij_java_binary_operation_wrap = off +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 0 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_at_first_column = true +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = false +ij_java_call_parameters_wrap = off +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_count_to_use_import_on_demand = 5 +ij_java_class_names_in_javadoc = 1 +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_while_brace_force = never +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = true +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_entity_dd_suffix = EJB +ij_java_entity_eb_suffix = Bean +ij_java_entity_hi_suffix = Home +ij_java_entity_lhi_prefix = Local +ij_java_entity_lhi_suffix = Home +ij_java_entity_li_prefix = Local +ij_java_entity_pk_class = java.lang.String +ij_java_entity_vo_suffix = VO +ij_java_enum_constants_wrap = off +ij_java_extends_keyword_wrap = off +ij_java_extends_list_wrap = off +ij_java_field_annotation_wrap = split_into_lines +ij_java_finally_on_new_line = false +ij_java_for_brace_force = never +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = off +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_if_brace_force = never +ij_java_imports_layout = *, |, javax.**, java.**, |, $* +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = false +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 2 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_control_statement_in_one_line = true +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = false +ij_java_keep_simple_lambdas_in_one_line = false +ij_java_keep_simple_methods_in_one_line = false +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = false +ij_java_line_comment_at_first_column = true +ij_java_message_dd_suffix = EJB +ij_java_message_eb_suffix = Bean +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = off +ij_java_method_parameters_new_line_after_left_paren = false +ij_java_method_parameters_right_paren_on_new_line = false +ij_java_method_parameters_wrap = off +ij_java_modifier_list_wrap = false +ij_java_names_count_to_use_import_on_demand = 3 +ij_java_new_line_after_lparen_in_record_header = false +ij_java_packages_to_use_import_on_demand = java.awt.*, javax.swing.* +ij_java_parameter_annotation_wrap = off +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_record_components_wrap = normal +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = false +ij_java_resource_list_wrap = off +ij_java_rparen_on_new_line_in_record_header = false +ij_java_session_dd_suffix = EJB +ij_java_session_eb_suffix = Bean +ij_java_session_hi_suffix = Home +ij_java_session_lhi_prefix = Local +ij_java_session_lhi_suffix = Home +ij_java_session_li_prefix = Local +ij_java_session_si_suffix = Service +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = false +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_record_header = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_subclass_name_suffix = Impl +ij_java_ternary_operation_signs_on_next_line = false +ij_java_ternary_operation_wrap = off +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = off +ij_java_throws_list_wrap = off +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = off +ij_java_visibility = public +ij_java_while_brace_force = never +ij_java_while_on_new_line = false +ij_java_wrap_comments = false +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = false + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +ij_editorconfig_space_before_colon = false +ij_editorconfig_space_before_comma = false +ij_editorconfig_spaces_around_assignment_operators = true + +[{*.ant, *.fxml, *.jhm, *.jnlp, *.jrxml, *.pom, *.rng, *.tld, *.wadl, *.wsdd, *.wsdl, *.xjb, *.xml, *.xsd, *.xsl, *.xslt, *.xul}] +ij_xml_align_attributes = true +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 2 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = true +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = false +ij_xml_text_wrap = normal +ij_xml_use_custom_settings = false + +[{*.bash, *.sh, *.zsh}] +indent_size = 2 +tab_width = 2 +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false + +[{*.markdown, *.md}] +ij_markdown_force_one_space_after_blockquote_symbol = true +ij_markdown_force_one_space_after_header_symbol = true +ij_markdown_force_one_space_after_list_bullet = true +ij_markdown_force_one_space_between_words = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_max_lines_around_block_elements = 1 +ij_markdown_max_lines_around_header = 1 +ij_markdown_max_lines_between_paragraphs = 1 +ij_markdown_min_lines_around_block_elements = 1 +ij_markdown_min_lines_around_header = 1 +ij_markdown_min_lines_between_paragraphs = 1 + +[*.properties] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = false +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + +[{*.yaml, *.yml}] +indent_size = 2 +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true diff --git a/LICENSE b/LICENSE index 261eeb9..a3cc671 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2021 Quarkiverse.io Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index e7408ab..d3b94c8 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,7 @@ This is a Quarkus extension for the [RabbitMQ](https://www.rabbitmq.com/) [Java Client](https://rabbitmq.com/api-guide.html). -Main use of this extension is to be able to inject the client without worrying about the -complexity related to handling dependencies in case of **native builds**. +RabbitMQ is a popular message broker. This Quarkus extension provides a client for RabbitMQ which is configurable using the `application.properties`. ## Coordinates @@ -16,6 +15,82 @@ complexity related to handling dependencies in case of **native builds**. LATEST ``` + +## Usage +Assuming you have RabbitMQ running on localhost:5672 you should add the following properties to your `application.properties` and fill in the values for `` and ``. + +```properties +quarkus.rabbitmqclient.virtual-host=/ +quarkus.rabbitmqclient.username= +quarkus.rabbitmqclient.password= +quarkus.rabbitmqclient.hostname=localhost +quarkus.rabbitmqclient.port=5672 +``` +Once you have configured the properties, you can start using the RabbitMQ client. + +```java +@ApplicationScoped +public class MessageService { + + private static final Logger log = LoggerFactory.getLogger(MessageService.class); + + @Inject + RabbitMQClient rabbitMQClient; + + private Channel channel; + + public void onApplicationStart(@Observes StartupEvent event) { + // on application start prepare the queus and message listener + setupQueues(); + setupReceiving(); + } + + private void setupQueues() { + try { + // create a connection + Connection connection = rabbitMQClient.connect(); + // create a channel + channel = connection.createChannel(); + // declare exchanges and queues + channel.exchangeDeclare("sample", BuiltinExchangeType.TOPIC, true); + channel.queueDeclare("sample.queue", true, false, false, null); + channel.queueBind("sample.queue", "test", "#"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private void setupReceiving() { + try { + // register a consumer for messages + channel.basicConsume("sample.queue", true, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + // just print the received message. + log.info("Received: " + new String(body, StandardCharsets.UTF_8)); + } + }); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void send(String message) { + try { + // send a message to the exchange + channel.basicPublish("test", "#", null, message.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} +``` + +You do not need to worry about closing connections as the `RabbitMQClient` will close them for you on application shutdown. + +## License +This extension is licensed under the Apache License 2.0. + ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): diff --git a/deployment/pom.xml b/deployment/pom.xml new file mode 100644 index 0000000..d09d48a --- /dev/null +++ b/deployment/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + io.quarkiverse.rabbitmqclient + quarkus-rabbitmq-client-parent + 0.1.0-SNAPSHOT + ../pom.xml + + + quarkus-rabbitmq-client-deployment + Quarkus RabbitMQ Client - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkiverse.rabbitmqclient + quarkus-rabbitmq-client + 0.1.0-SNAPSHOT + + + io.quarkus + quarkus-smallrye-health-spi + + + io.quarkus + quarkus-junit5-internal + test + + + io.quarkus + quarkus-smallrye-health + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + diff --git a/deployment/src/main/java/io/quarkiverse/rabbitmqclient/deployment/QuarkusRabbitmqClientProcessor.java b/deployment/src/main/java/io/quarkiverse/rabbitmqclient/deployment/QuarkusRabbitmqClientProcessor.java new file mode 100644 index 0000000..9d7031e --- /dev/null +++ b/deployment/src/main/java/io/quarkiverse/rabbitmqclient/deployment/QuarkusRabbitmqClientProcessor.java @@ -0,0 +1,36 @@ +package io.quarkiverse.rabbitmqclient.deployment; + +import io.quarkiverse.rabbitmqclient.RabbitMQClientBuildConfig; +import io.quarkiverse.rabbitmqclient.RabbitMQClientProducer; +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.IndexDependencyBuildItem; +import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; + +class QuarkusRabbitmqClientProcessor { + + private static final String FEATURE = "rabbitmq-client"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + + @BuildStep + void addDependencies(BuildProducer indexDependency) { + indexDependency.produce(new IndexDependencyBuildItem("com.rabbitmq", "amqp-client")); + } + + @BuildStep + HealthBuildItem addHealthCheck(RabbitMQClientBuildConfig buildTimeConfig) { + return new HealthBuildItem("io.quarkiverse.rabbitmqclient.RabbitMQReadyCheck", + buildTimeConfig.healthEnabled); + } + + @BuildStep + public void registerBeans(BuildProducer additionalBeanBuildItemProducer) { + additionalBeanBuildItemProducer.produce(AdditionalBeanBuildItem.unremovableOf(RabbitMQClientProducer.class)); + } +} diff --git a/deployment/src/test/java/io/quarkiverse/rabbitmqclient/DummyServer.java b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/DummyServer.java new file mode 100644 index 0000000..ac38dbe --- /dev/null +++ b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/DummyServer.java @@ -0,0 +1,68 @@ +package io.quarkiverse.rabbitmqclient; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.util.Random; + +import javax.net.ServerSocketFactory; + +class DummyServer { + + public static final int PORT_RANGE_MIN = 1025; + public static final int PORT_RANGE_MAX = 65535; + private static final Random random = new Random(System.nanoTime()); + + private ServerSocket socket = null; + private int port; + + public int getPort() { + return port; + } + + public void close() { + try { + socket.close(); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + public static DummyServer newDummyServer() { + try { + DummyServer ds = new DummyServer(); + ds.port = findFreePort(); + ds.socket = ServerSocketFactory.getDefault().createServerSocket(ds.port, 50, InetAddress.getByName("localhost")); + return ds; + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + private static int findFreePort() { + int candidate; + int tryCount = 0; + do { + if (tryCount > (PORT_RANGE_MAX - PORT_RANGE_MIN)) { + throw new IllegalStateException("Could not find a free port."); + } + candidate = random.nextInt(PORT_RANGE_MAX - PORT_RANGE_MIN) + PORT_RANGE_MIN; + tryCount++; + + } while (!isAvailablePort(candidate)); + + return candidate; + } + + private static boolean isAvailablePort(int port) { + try { + ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket( + port, 50, InetAddress.getByName("localhost")); + serverSocket.close(); + return true; + } catch (Exception ex) { + return false; + } + } +} diff --git a/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqMinimalClientConfigTest.java b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqMinimalClientConfigTest.java new file mode 100644 index 0000000..2909469 --- /dev/null +++ b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqMinimalClientConfigTest.java @@ -0,0 +1,34 @@ +package io.quarkiverse.rabbitmqclient; + +import java.util.Properties; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.TlsConfig; +import io.quarkus.test.QuarkusUnitTest; + +public class QuarkusRabbitmqMinimalClientConfigTest extends RabbitMQConfigTest { + + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() // Start unit test with your extension loaded + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource(QuarkusRabbitmqMinimalClientConfigTest.class.getResource("/minimal-properties.properties"), + "application.properties")); + + @Inject + RabbitMQClientConfig config; + + @Inject + TlsConfig tlsConfig; + + @Test + public void testConnectionFactoryProperties() { + Properties properties = RabbitMQHelper.newProperties(config, tlsConfig); + assertRabbitMQConfig(config, tlsConfig, properties); + } +} diff --git a/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqNonDefaultClientConfigTest.java b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqNonDefaultClientConfigTest.java new file mode 100644 index 0000000..72c6610 --- /dev/null +++ b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqNonDefaultClientConfigTest.java @@ -0,0 +1,36 @@ +package io.quarkiverse.rabbitmqclient; + +import java.util.Properties; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.TlsConfig; +import io.quarkus.test.QuarkusUnitTest; + +public class QuarkusRabbitmqNonDefaultClientConfigTest extends RabbitMQConfigTest { + + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() // Start unit test with your extension loaded + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource( + QuarkusRabbitmqNonDefaultClientConfigTest.class.getResource("/non-default-properties.properties"), + "application.properties")); + + @Inject + RabbitMQClientConfig config; + + @Inject + TlsConfig tlsConfig; + + @Test + public void testConnectionFactoryProperties() { + Properties properties = RabbitMQHelper.newProperties(config, tlsConfig); + assertRabbitMQConfig(config, tlsConfig, properties); + } + +} diff --git a/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqReadyCheckTest.java b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqReadyCheckTest.java new file mode 100644 index 0000000..2b552e7 --- /dev/null +++ b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqReadyCheckTest.java @@ -0,0 +1,58 @@ +package io.quarkiverse.rabbitmqclient; + +import javax.inject.Inject; + +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.Readiness; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class QuarkusRabbitmqReadyCheckTest { + + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() // Start unit test with your extension loaded + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(DummyServer.class) + .addAsResource(QuarkusRabbitmqReadyCheckTest.class.getResource("/ready-check-properties.properties"), + "application.properties")); + + @Inject + RabbitMQClientConfig config; + + @Readiness + @Inject + RabbitMQReadyCheck readyCheck; + + private DummyServer dummyServer; + + @BeforeEach + public void before() { + dummyServer = DummyServer.newDummyServer(); + config.port = dummyServer.getPort(); + } + + @AfterEach + public void after() { + dummyServer.close(); + } + + @Test + public void testHealthEndpointUp() { + HealthCheckResponse resp = readyCheck.call(); + Assertions.assertEquals(HealthCheckResponse.State.UP, resp.getState()); + } + + @Test + public void testHealthEndpointDown() { + dummyServer.close(); + HealthCheckResponse resp = readyCheck.call(); + Assertions.assertEquals(HealthCheckResponse.State.DOWN, resp.getState()); + } +} diff --git a/deployment/src/test/java/io/quarkiverse/rabbitmqclient/RabbitMQConfigTest.java b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/RabbitMQConfigTest.java new file mode 100644 index 0000000..afc78ce --- /dev/null +++ b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/RabbitMQConfigTest.java @@ -0,0 +1,107 @@ +package io.quarkiverse.rabbitmqclient; + +import java.util.Properties; + +import org.junit.jupiter.api.Assertions; + +import com.rabbitmq.client.ConnectionFactoryConfigurator; + +import io.quarkus.runtime.TlsConfig; + +public abstract class RabbitMQConfigTest { + + protected void assertRabbitMQConfig(RabbitMQClientConfig config, TlsConfig tlsConfig, Properties properties) { + Assertions.assertEquals(config.username, properties.getProperty(ConnectionFactoryConfigurator.USERNAME)); + Assertions.assertEquals(config.password, properties.getProperty(ConnectionFactoryConfigurator.PASSWORD)); + Assertions.assertEquals(config.virtualHost, properties.getProperty(ConnectionFactoryConfigurator.VIRTUAL_HOST)); + Assertions.assertEquals(config.hostname, properties.getProperty(ConnectionFactoryConfigurator.HOST)); + Assertions.assertEquals(asString(config.port), properties.getProperty(ConnectionFactoryConfigurator.PORT)); + Assertions.assertEquals(asString(config.requestedChannelMax), + properties.getProperty(ConnectionFactoryConfigurator.CONNECTION_CHANNEL_MAX)); + Assertions.assertEquals(asString(config.requestedFrameMax), + properties.getProperty(ConnectionFactoryConfigurator.CONNECTION_FRAME_MAX)); + Assertions.assertEquals(asString(config.requestedHeartbeat), + properties.getProperty(ConnectionFactoryConfigurator.CONNECTION_HEARTBEAT)); + Assertions.assertEquals(asString(config.connectionTimeout), + properties.getProperty(ConnectionFactoryConfigurator.CONNECTION_TIMEOUT)); + Assertions.assertEquals(asString(config.handshakeTimeout), + properties.getProperty(ConnectionFactoryConfigurator.HANDSHAKE_TIMEOUT)); + Assertions.assertEquals(asString(config.shutdownTimeout), + properties.getProperty(ConnectionFactoryConfigurator.SHUTDOWN_TIMEOUT)); + Assertions.assertEquals(asString(config.connectionRecovery), + properties.getProperty(ConnectionFactoryConfigurator.CONNECTION_RECOVERY_ENABLED)); + Assertions.assertEquals(asString(config.topologyRecovery), + properties.getProperty(ConnectionFactoryConfigurator.TOPOLOGY_RECOVERY_ENABLED)); + Assertions.assertEquals(asString(config.networkRecoveryInterval), + properties.getProperty(ConnectionFactoryConfigurator.CONNECTION_RECOVERY_INTERVAL)); + Assertions.assertEquals(asString(config.channelRpcTimeout), + properties.getProperty(ConnectionFactoryConfigurator.CHANNEL_RPC_TIMEOUT)); + Assertions.assertEquals(asString(config.channelRpcResponseTypeCheck), + properties.getProperty(ConnectionFactoryConfigurator.CHANNEL_SHOULD_CHECK_RPC_RESPONSE_TYPE)); + + // NIO configuration + if (config.nio.enabled) { + Assertions.assertEquals(asString(config.nio.threads), + properties.getProperty(ConnectionFactoryConfigurator.NIO_NB_IO_THREADS)); + Assertions.assertEquals(asString(config.nio.readByteBufferSize), + properties.getProperty(ConnectionFactoryConfigurator.NIO_READ_BYTE_BUFFER_SIZE)); + Assertions.assertEquals(asString(config.nio.writeByteBufferSize), + properties.getProperty(ConnectionFactoryConfigurator.NIO_WRITE_BYTE_BUFFER_SIZE)); + Assertions.assertEquals(asString(config.nio.writeQueueCapacity), + properties.getProperty(ConnectionFactoryConfigurator.NIO_WRITE_QUEUE_CAPACITY)); + Assertions.assertEquals(asString(config.nio.writeEnqueuingTimeout), + properties.getProperty(ConnectionFactoryConfigurator.NIO_WRITE_ENQUEUING_TIMEOUT_IN_MS)); + } + + if (config.tls.enabled) { + Assertions.assertEquals(config.tls.algorithm, properties.getProperty(ConnectionFactoryConfigurator.SSL_ALGORITHM)); + Assertions.assertEquals(asString(config.tls.enabled), + properties.getProperty(ConnectionFactoryConfigurator.SSL_ENABLED)); + + if (tlsConfig.trustAll) { + Assertions.assertEquals(Boolean.FALSE.toString(), + properties.getProperty(ConnectionFactoryConfigurator.SSL_VALIDATE_SERVER_CERTIFICATE)); + Assertions.assertEquals(Boolean.FALSE.toString(), + properties.getProperty(ConnectionFactoryConfigurator.SSL_VERIFY_HOSTNAME)); + } else { + Assertions.assertEquals(asString(config.tls.validateServerCertificate), + properties.getProperty(ConnectionFactoryConfigurator.SSL_VALIDATE_SERVER_CERTIFICATE)); + Assertions.assertEquals(asString(config.tls.verifyHostname), + properties.getProperty(ConnectionFactoryConfigurator.SSL_VERIFY_HOSTNAME)); + + Assertions.assertEquals(config.tls.keyStoreFile.orElse(null), + properties.getProperty(ConnectionFactoryConfigurator.SSL_KEY_STORE)); + Assertions.assertEquals(config.tls.keyStorePassword.orElse(null), + properties.getProperty(ConnectionFactoryConfigurator.SSL_KEY_STORE_PASSWORD)); + Assertions.assertEquals(config.tls.keyStoreType, + properties.getProperty(ConnectionFactoryConfigurator.SSL_KEY_STORE_TYPE)); + Assertions.assertEquals(config.tls.keyStoreAlgorithm, + properties.getProperty(ConnectionFactoryConfigurator.SSL_KEY_STORE_ALGORITHM)); + + Assertions.assertEquals(config.tls.trustStoreFile.orElse(null), + properties.getProperty(ConnectionFactoryConfigurator.SSL_TRUST_STORE)); + Assertions.assertEquals(config.tls.trustStorePassword.orElse(null), + properties.getProperty(ConnectionFactoryConfigurator.SSL_TRUST_STORE_PASSWORD)); + Assertions.assertEquals(config.tls.trustStoreType, + properties.getProperty(ConnectionFactoryConfigurator.SSL_TRUST_STORE_TYPE)); + Assertions.assertEquals(config.tls.trustStoreAlgorithm, + properties.getProperty(ConnectionFactoryConfigurator.SSL_TRUST_STORE_ALGORITHM)); + } + } + + // client properties + Assertions.assertEquals(RabbitMQHelper.CLIENT_PROPERTY_PREFIX, + properties.getProperty(ConnectionFactoryConfigurator.CLIENT_PROPERTIES_PREFIX)); + config.properties.forEach((name, value) -> { + Assertions.assertEquals(value, properties.getProperty(RabbitMQHelper.CLIENT_PROPERTY_PREFIX + name)); + }); + } + + private String asString(int value) { + return Integer.toString(value); + } + + private String asString(boolean value) { + return Boolean.toString(value); + } +} diff --git a/deployment/src/test/resources/minimal-properties.properties b/deployment/src/test/resources/minimal-properties.properties new file mode 100644 index 0000000..6b2694a --- /dev/null +++ b/deployment/src/test/resources/minimal-properties.properties @@ -0,0 +1,10 @@ +quarkus.log.level=INFO +quarkus.log.category."io.quarkiverse.rabbitmqclient".min-level=DEBUG +quarkus.log.category."io.quarkiverse.rabbitmqclient".level=DEBUG + +#RabbitMQ +quarkus.rabbitmqclient.virtual-host=/vhost +quarkus.rabbitmqclient.username=rabbitmq +quarkus.rabbitmqclient.password=rabbitmq +quarkus.rabbitmqclient.hostname=somehost +quarkus.rabbitmqclient.port=1234 \ No newline at end of file diff --git a/deployment/src/test/resources/non-default-properties.properties b/deployment/src/test/resources/non-default-properties.properties new file mode 100644 index 0000000..dd9ef64 --- /dev/null +++ b/deployment/src/test/resources/non-default-properties.properties @@ -0,0 +1,53 @@ +quarkus.log.level=INFO +quarkus.log.category."io.quarkiverse.rabbitmqclient".min-level=DEBUG +quarkus.log.category."io.quarkiverse.rabbitmqclient".level=DEBUG + +#quarkus.tls.trust-all=true + +#RabbitMQ +quarkus.rabbitmqclient.virtual-host=/vhost +quarkus.rabbitmqclient.username=someuser +quarkus.rabbitmqclient.password=somepass +quarkus.rabbitmqclient.hostname=somehost +quarkus.rabbitmqclient.port=1234 +quarkus.rabbitmqclient.connection-recovery=false +quarkus.rabbitmqclient.connection-timeout=10000 +quarkus.rabbitmqclient.handshake-timeout=5000 +quarkus.rabbitmqclient.network-recovery-interval=3000 +quarkus.rabbitmqclient.requested-channel-max=100 +quarkus.rabbitmqclient.requested-heartbeat=30 +quarkus.rabbitmqclient.channel-rpc-response-type-check=true +quarkus.rabbitmqclient.channel-rpc-timeout=30000 +quarkus.rabbitmqclient.topology-recovery=false +quarkus.rabbitmqclient.requested-frame-max=8096 +quarkus.rabbitmqclient.shutdown-timeout=5000 + +#RabbitMQ Brokers +quarkus.rabbitmqclient.addresses.broker1.hostname=localhost +quarkus.rabbitmqclient.addresses.broker1.port=5672 + +#RabbitMQ NIO +quarkus.rabbitmqclient.nio.enabled=true +quarkus.rabbitmqclient.nio.threads=2 +quarkus.rabbitmqclient.nio.read-byte-buffer-size=8096 +quarkus.rabbitmqclient.nio.write-byte-buffer-size=8096 +quarkus.rabbitmqclient.nio.write-enqueuing-timeout=5000 +quarkus.rabbitmqclient.nio.write-queue-capacity=7500 + +##RabbitMQ TLS +quarkus.rabbitmqclient.tls.enabled=true +quarkus.rabbitmqclient.tls.algorithm=TLSv1.0 +quarkus.rabbitmqclient.tls.validate-server-certificate=false +quarkus.rabbitmqclient.tls.verify-hostname=false +quarkus.rabbitmqclient.tls.key-store-file=/keys.pkcs12 +quarkus.rabbitmqclient.tls.key-store-password=letmein +quarkus.rabbitmqclient.tls.key-store-algorithm=PKIX +quarkus.rabbitmqclient.tls.key-store-type=PKCS12 +quarkus.rabbitmqclient.tls.trust-store-file=/ca.jce +quarkus.rabbitmqclient.tls.trust-store-password=letmein +quarkus.rabbitmqclient.tls.trust-store-algorithm=PKIX +quarkus.rabbitmqclient.tls.trust-store-type=JCE + +#RabbitMQ Client Properties +quarkus.rabbitmqclient.properties.my-prop=test +quarkus.rabbitmqclient.properties.my-prop2=test2 \ No newline at end of file diff --git a/deployment/src/test/resources/ready-check-properties.properties b/deployment/src/test/resources/ready-check-properties.properties new file mode 100644 index 0000000..84bb6c3 --- /dev/null +++ b/deployment/src/test/resources/ready-check-properties.properties @@ -0,0 +1,12 @@ +quarkus.log.level=INFO +quarkus.log.category."io.quarkiverse.rabbitmqclient".min-level=DEBUG +quarkus.log.category."io.quarkiverse.rabbitmqclient".level=DEBUG + +quarkus.http.test-port=34567 + +#RabbitMQ +quarkus.rabbitmqclient.virtual-host=/vhost +quarkus.rabbitmqclient.username=someuser +quarkus.rabbitmqclient.password=somepass +quarkus.rabbitmqclient.hostname=localhost +quarkus.rabbitmqclient.port=5672 \ No newline at end of file diff --git a/docs/modules/ROOT/pages/config.adoc b/docs/modules/ROOT/pages/config.adoc deleted file mode 100644 index 926cca3..0000000 --- a/docs/modules/ROOT/pages/config.adoc +++ /dev/null @@ -1,112 +0,0 @@ -// -// This content is generated using mvn compile and copied manually to here -// -[.configuration-legend] -icon:lock[title=Fixed at build time] Configuration property fixed at build time - All other configuration properties are overridable at runtime -[.configuration-reference.searchable, cols="80,.^10,.^10"] -|=== - -h|[[quarkus-freemarker_configuration]]link:#quarkus-freemarker_configuration[Configuration property] - -h|Type -h|Default - -a|icon:lock[title=Fixed at build time] [[quarkus-freemarker_quarkus.freemarker.resource-paths]]`link:#quarkus-freemarker_quarkus.freemarker.resource-paths[quarkus.freemarker.resource-paths]` - -[.description] --- -Comma-separated list of absolute resource paths to scan recursively for templates. All tree folder from 'resource-paths' will be added as a resource. Unprefixed locations or locations starting with classpath will be processed in the same way. ---|list of string -|`freemarker/templates` - - -a| [[quarkus-freemarker_quarkus.freemarker.file-paths]]`link:#quarkus-freemarker_quarkus.freemarker.file-paths[quarkus.freemarker.file-paths]` - -[.description] --- -Comma-separated of file system paths where freemarker templates are located ---|list of string -| - - -a| [[quarkus-freemarker_quarkus.freemarker.default-encoding]]`link:#quarkus-freemarker_quarkus.freemarker.default-encoding[quarkus.freemarker.default-encoding]` - -[.description] --- -Set the preferred charset template files are stored in. ---|string -| - - -a| [[quarkus-freemarker_quarkus.freemarker.template-exception-handler]]`link:#quarkus-freemarker_quarkus.freemarker.template-exception-handler[quarkus.freemarker.template-exception-handler]` - -[.description] --- -Sets how errors will appear. rethrow, debug, html-debug, ignore. ---|string -| - - -a| [[quarkus-freemarker_quarkus.freemarker.log-template-exceptions]]`link:#quarkus-freemarker_quarkus.freemarker.log-template-exceptions[quarkus.freemarker.log-template-exceptions]` - -[.description] --- -If false, don't log exceptions inside FreeMarker that it will be thrown at you anyway. ---|boolean -| - - -a| [[quarkus-freemarker_quarkus.freemarker.wrap-unchecked-exceptions]]`link:#quarkus-freemarker_quarkus.freemarker.wrap-unchecked-exceptions[quarkus.freemarker.wrap-unchecked-exceptions]` - -[.description] --- -Wrap unchecked exceptions thrown during template processing into TemplateException-s. ---|boolean -| - - -a| [[quarkus-freemarker_quarkus.freemarker.fallback-on-null-loop-variable]]`link:#quarkus-freemarker_quarkus.freemarker.fallback-on-null-loop-variable[quarkus.freemarker.fallback-on-null-loop-variable]` - -[.description] --- -If false, do not fall back to higher scopes when reading a null loop variable. ---|boolean -| - - -a| [[quarkus-freemarker_quarkus.freemarker.boolean-format]]`link:#quarkus-freemarker_quarkus.freemarker.boolean-format[quarkus.freemarker.boolean-format]` - -[.description] --- -The string value for the boolean `true` and `false` values, usually intended for human consumption (not for a computer language), separated with comma. ---|string -| - - -a| [[quarkus-freemarker_quarkus.freemarker.number-format]]`link:#quarkus-freemarker_quarkus.freemarker.number-format[quarkus.freemarker.number-format]` - -[.description] --- -Sets the default number format used to convert numbers to strings. ---|string -| - - -a| [[quarkus-freemarker_quarkus.freemarker.object-wrapper-expose-fields]]`link:#quarkus-freemarker_quarkus.freemarker.object-wrapper-expose-fields[quarkus.freemarker.object-wrapper-expose-fields]` - -[.description] --- -If true, the object wrapper will be configured to expose fields. ---|boolean -| - - -a|icon:lock[title=Fixed at build time] [[quarkus-freemarker_quarkus.freemarker.directive-directive]]`link:#quarkus-freemarker_quarkus.freemarker.directive-directive[quarkus.freemarker.directive]` - -[.description] --- -List of directives to register with format name=classname ---|`Map` -| - -|=== diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 8bd2ffc..14aee87 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -1,22 +1,97 @@ -= Your Quarkiverse Extension += Quarkus RabbitMQ Client :extension-status: preview -Describe what the extension does here. +RabbitMQ is a popular message broker. This Quarkus extension provides a client for RabbitMQ which is configurable using the `application.properties`. == Installation -If you want to use this extension, you need to add the `quarkiverse-` extension first. +If you want to use this extension, you need to add the `quarkus-rabbitmq-client` extension first. In your `pom.xml` file, add: [source,xml] ---- io.quarkiverse - quarkiverse-freemarker + quarkus-rabbitmq-client ---- +== Usage +Assuming you have RabbitMQ running on localhost:5672 you should add the following properties to your `application.properties` and fill in the values for `` and ``. + +[source,properties] +---- +quarkus.rabbitmqclient.virtual-host=/ +quarkus.rabbitmqclient.username= +quarkus.rabbitmqclient.password= +quarkus.rabbitmqclient.hostname=localhost +quarkus.rabbitmqclient.port=5672 +---- + +Once you have configured the properties, you can start using the RabbitMQ client. + +[source,java] +---- +@ApplicationScoped +public class MessageService { + + private static final Logger log = LoggerFactory.getLogger(MessageService.class); + + @Inject + RabbitMQClient rabbitMQClient; + + private Channel channel; + + public void onApplicationStart(@Observes StartupEvent event) { + // on application start prepare the queus and message listener + setupQueues(); + setupReceiving(); + } + + private void setupQueues() { + try { + // create a connection + Connection connection = rabbitMQClient.connect(); + // create a channel + channel = connection.createChannel(); + // declare exchanges and queues + channel.exchangeDeclare("sample", BuiltinExchangeType.TOPIC, true); + channel.queueDeclare("sample.queue", true, false, false, null); + channel.queueBind("sample.queue", "test", "#"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private void setupReceiving() { + try { + // register a consumer for messages + channel.basicConsume("sample.queue", true, new DefaultConsumer(channel) { + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + // just print the received message. + log.info("Received: " + new String(body, StandardCharsets.UTF_8)); + } + }); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void send(String message) { + try { + // send a message to the exchange + channel.basicPublish("test", "#", null, message.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} +---- + +You do not need to worry about closing connections as the `RabbitMQClient` will close them for you on application shutdown. + [[extension-configuration-reference]] == Extension Configuration Reference -include::config.adoc[leveloffset=+1, opts=optional] +include::quarkus-rabbitmqclient.adoc[leveloffset=+1, opts=optional] diff --git a/docs/modules/ROOT/pages/quarkus-rabbitmqclient.adoc b/docs/modules/ROOT/pages/quarkus-rabbitmqclient.adoc new file mode 100644 index 0000000..58c1041 --- /dev/null +++ b/docs/modules/ROOT/pages/quarkus-rabbitmqclient.adoc @@ -0,0 +1,376 @@ +[.configuration-legend] +icon:lock[title=Fixed at build time] Configuration property fixed at build time - All other configuration properties are overridable at runtime +[.configuration-reference.searchable, cols="80,.^10,.^10"] +|=== + +h|[[quarkus-rabbitmqclient_configuration]]link:#quarkus-rabbitmqclient_configuration[Configuration property] + +h|Type +h|Default + +a|icon:lock[title=Fixed at build time] [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.health-enabled]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.health-enabled[quarkus.rabbitmqclient.health-enabled]` + +[.description] +-- +Enables health check +--|boolean +|`true` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.uri]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.uri[quarkus.rabbitmqclient.uri]` + +[.description] +-- +URI for connecting, formatted as amqp://userName:password@hostName:portNumber/virtualHost +--|string +| + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.username]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.username[quarkus.rabbitmqclient.username]` + +[.description] +-- +Username for authentication +--|string +|`guest` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.password]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.password[quarkus.rabbitmqclient.password]` + +[.description] +-- +Password for authentication +--|string +|`guest` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.hostname]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.hostname[quarkus.rabbitmqclient.hostname]` + +[.description] +-- +Hostname for connecting +--|string +|`localhost` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.virtual-host]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.virtual-host[quarkus.rabbitmqclient.virtual-host]` + +[.description] +-- +Virtual host +--|string +|`/` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.port]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.port[quarkus.rabbitmqclient.port]` + +[.description] +-- +Port number for connecting +--|int +|`-1` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.connection-timeout]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.connection-timeout[quarkus.rabbitmqclient.connection-timeout]` + +[.description] +-- +Connection timeout in milliseconds +--|int +|`60000` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.requested-heartbeat]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.requested-heartbeat[quarkus.rabbitmqclient.requested-heartbeat]` + +[.description] +-- +Heartbeat interval in seconds +--|int +|`60` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.handshake-timeout]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.handshake-timeout[quarkus.rabbitmqclient.handshake-timeout]` + +[.description] +-- +Handshake timeout in milliseconds +--|int +|`10000` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.shutdown-timeout]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.shutdown-timeout[quarkus.rabbitmqclient.shutdown-timeout]` + +[.description] +-- +Shutdown timeout in milliseconds +--|int +|`10000` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.requested-channel-max]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.requested-channel-max[quarkus.rabbitmqclient.requested-channel-max]` + +[.description] +-- +Maximum number of channels per connection +--|int +|`2047` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.requested-frame-max]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.requested-frame-max[quarkus.rabbitmqclient.requested-frame-max]` + +[.description] +-- +Maximum frame size +--|int +|`0` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.network-recovery-interval]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.network-recovery-interval[quarkus.rabbitmqclient.network-recovery-interval]` + +[.description] +-- +Network recovery interval in milliseconds +--|int +|`5000` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.channel-rpc-timeout]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.channel-rpc-timeout[quarkus.rabbitmqclient.channel-rpc-timeout]` + +[.description] +-- +Channel RPC timeout in milliseconds +--|int +|`600000` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.channel-rpc-response-type-check]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.channel-rpc-response-type-check[quarkus.rabbitmqclient.channel-rpc-response-type-check]` + +[.description] +-- +Validate channel RPC response type +--|boolean +|`false` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.connection-recovery]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.connection-recovery[quarkus.rabbitmqclient.connection-recovery]` + +[.description] +-- +Recover connection on failure +--|boolean +|`true` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.topology-recovery]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.topology-recovery[quarkus.rabbitmqclient.topology-recovery]` + +[.description] +-- +Recover topology on failure +--|boolean +|`true` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.properties-property-name]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.properties-property-name[quarkus.rabbitmqclient.properties]` + +[.description] +-- +Client properties +--|`Map` +| + + +h|[[quarkus-rabbitmqclient_quarkus.rabbitmqclient.addresses-broker-addresses-for-creating-connections]]link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.addresses-broker-addresses-for-creating-connections[Broker addresses for creating connections] + +h|Type +h|Default + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.addresses.-broker-name-.hostname]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.addresses.-broker-name-.hostname[quarkus.rabbitmqclient.addresses."broker-name".hostname]` + +[.description] +-- +Hostname for connecting +--|string +|required icon:exclamation-circle[title=Configuration property is required] + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.addresses.-broker-name-.port]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.addresses.-broker-name-.port[quarkus.rabbitmqclient.addresses."broker-name".port]` + +[.description] +-- +Port number for connecting +--|int +|`0` + + +h|[[quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls-tls-configuration]]link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls-tls-configuration[Tls configuration] + +h|Type +h|Default + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.enabled]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.enabled[quarkus.rabbitmqclient.tls.enabled]` + +[.description] +-- +Enables TLS +--|boolean +|`false` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.algorithm]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.algorithm[quarkus.rabbitmqclient.tls.algorithm]` + +[.description] +-- +TLS Algorithm to use +--|string +|`TLSv1.2` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.trust-store-file]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.trust-store-file[quarkus.rabbitmqclient.tls.trust-store-file]` + +[.description] +-- +Trust store file +--|string +| + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.trust-store-type]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.trust-store-type[quarkus.rabbitmqclient.tls.trust-store-type]` + +[.description] +-- +Trust store type +--|string +|`JKS` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.trust-store-algorithm]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.trust-store-algorithm[quarkus.rabbitmqclient.tls.trust-store-algorithm]` + +[.description] +-- +Trust store algorithm +--|string +|`SunX509` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.trust-store-password]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.trust-store-password[quarkus.rabbitmqclient.tls.trust-store-password]` + +[.description] +-- +Trust store password +--|string +| + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.key-store-file]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.key-store-file[quarkus.rabbitmqclient.tls.key-store-file]` + +[.description] +-- +Key store file +--|string +| + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.key-store-password]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.key-store-password[quarkus.rabbitmqclient.tls.key-store-password]` + +[.description] +-- +Key store password +--|string +| + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.key-store-type]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.key-store-type[quarkus.rabbitmqclient.tls.key-store-type]` + +[.description] +-- +Key store type +--|string +|`PKCS12` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.key-store-algorithm]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.key-store-algorithm[quarkus.rabbitmqclient.tls.key-store-algorithm]` + +[.description] +-- +Key store algorithm +--|string +|`SunX509` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.validate-server-certificate]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.validate-server-certificate[quarkus.rabbitmqclient.tls.validate-server-certificate]` + +[.description] +-- +Validate server certificate +--|boolean +|`true` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.verify-hostname]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.tls.verify-hostname[quarkus.rabbitmqclient.tls.verify-hostname]` + +[.description] +-- +Verify hostname +--|boolean +|`true` + + +h|[[quarkus-rabbitmqclient_quarkus.rabbitmqclient.nio-non-blocking-io-configuration]]link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.nio-non-blocking-io-configuration[Non blocking IO configuration] + +h|Type +h|Default + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.nio.enabled]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.nio.enabled[quarkus.rabbitmqclient.nio.enabled]` + +[.description] +-- +Enables non blocking IO +--|boolean +|`false` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.nio.read-byte-buffer-size]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.nio.read-byte-buffer-size[quarkus.rabbitmqclient.nio.read-byte-buffer-size]` + +[.description] +-- +Read buffer size in bytes +--|int +|`32768` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.nio.write-byte-buffer-size]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.nio.write-byte-buffer-size[quarkus.rabbitmqclient.nio.write-byte-buffer-size]` + +[.description] +-- +Write buffer size in bytes +--|int +|`32768` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.nio.threads]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.nio.threads[quarkus.rabbitmqclient.nio.threads]` + +[.description] +-- +Number of non blocking IO threads +--|int +|`1` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.nio.write-enqueuing-timeout]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.nio.write-enqueuing-timeout[quarkus.rabbitmqclient.nio.write-enqueuing-timeout]` + +[.description] +-- +Write enqueuing timeout in milliseconds +--|int +|`10000` + + +a| [[quarkus-rabbitmqclient_quarkus.rabbitmqclient.nio.write-queue-capacity]]`link:#quarkus-rabbitmqclient_quarkus.rabbitmqclient.nio.write-queue-capacity[quarkus.rabbitmqclient.nio.write-queue-capacity]` + +[.description] +-- +Write queue capacity. +--|int +|`10000` + +|=== \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5e85326 --- /dev/null +++ b/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + + io.quarkiverse + quarkiverse-parent + 6 + + + io.quarkiverse.rabbitmqclient + quarkus-rabbitmq-client-parent + 0.1.0-SNAPSHOT + Quarkus RabbitMQ Client - Parent + + pom + + + UTF-8 + UTF-8 + 1.8 + 1.8 + true + 1.11.0.Final + 3.8.1 + 5.10.0 + + + + deployment + runtime + + + + + io.quarkus + quarkus-bom + ${quarkus.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + + + + diff --git a/runtime/pom.xml b/runtime/pom.xml new file mode 100644 index 0000000..12e3fb1 --- /dev/null +++ b/runtime/pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + + io.quarkiverse.rabbitmqclient + quarkus-rabbitmq-client-parent + 0.1.0-SNAPSHOT + ../pom.xml + + + quarkus-rabbitmq-client + Quarkus RabbitMQ Client - Runtime + + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-smallrye-health + true + + + com.rabbitmq + amqp-client + 5.10.0 + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + ${quarkus.version} + + + + extension-descriptor + + compile + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClient.java b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClient.java new file mode 100644 index 0000000..8498a1a --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClient.java @@ -0,0 +1,35 @@ +package io.quarkiverse.rabbitmqclient; + +import com.rabbitmq.client.Connection; + +/** + * RabbitMQ client for handling connections with RabbitMQ brokers using the AMQP 0.9.1 protocol. + * + * @author b.passon + */ +public interface RabbitMQClient { + + /** + * Opens a connection to the configured RabbitMQ broker. + * + * @return a new randomly named connection. + */ + Connection connect(); + + /** + * Opens a connection to the configured RabbitMQ broker with a given name. + * + * @param name the name of the connection. + * @return a new connection if none exists with the given name, else the exiting one. + */ + Connection connect(String name); + + /** + * Explicitly disconnects the client from the RabbitMQ broker. + *

+ * Clients are also closed during Quarkus shutdown. + *

+ */ + void disconnect(); + +} diff --git a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientBuildConfig.java b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientBuildConfig.java new file mode 100644 index 0000000..a84fdfc --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientBuildConfig.java @@ -0,0 +1,15 @@ +package io.quarkiverse.rabbitmqclient; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "rabbitmqclient", phase = ConfigPhase.BUILD_TIME) +public class RabbitMQClientBuildConfig { + + /** + * Enables health check + */ + @ConfigItem(defaultValue = "true") + public boolean healthEnabled; +} diff --git a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientConfig.java b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientConfig.java new file mode 100644 index 0000000..425848f --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientConfig.java @@ -0,0 +1,280 @@ +package io.quarkiverse.rabbitmqclient; + +import java.util.Collections; +import java.util.Map; +import java.util.Optional; + +import com.rabbitmq.client.ConnectionFactory; + +import io.quarkus.runtime.annotations.*; + +@ConfigRoot(name = "rabbitmqclient", phase = ConfigPhase.RUN_TIME) +public class RabbitMQClientConfig { + + /** + * URI for connecting, formatted as amqp://userName:password@hostName:portNumber/virtualHost + */ + @ConfigItem + public Optional uri; + + /** + * Broker addresses for creating connections + */ + @ConfigItem + @ConfigDocMapKey("broker-name") + @ConfigDocSection + public Map addresses = Collections.emptyMap(); + + /** + * Username for authentication + */ + @ConfigItem(defaultValue = ConnectionFactory.DEFAULT_USER) + public String username; + + /** + * Password for authentication + */ + @ConfigItem(defaultValue = ConnectionFactory.DEFAULT_PASS) + public String password; + + /** + * Hostname for connecting + */ + @ConfigItem(defaultValue = ConnectionFactory.DEFAULT_HOST) + public String hostname; + + /** + * Virtual host + */ + @ConfigItem(defaultValue = ConnectionFactory.DEFAULT_VHOST) + public String virtualHost; + + /** + * Port number for connecting + */ + @ConfigItem(defaultValue = "" + ConnectionFactory.USE_DEFAULT_PORT) + public int port; + + /** + * Connection timeout in milliseconds + */ + @ConfigItem(defaultValue = "" + ConnectionFactory.DEFAULT_CONNECTION_TIMEOUT) + public int connectionTimeout; + + /** + * Heartbeat interval in seconds + */ + @ConfigItem(defaultValue = "" + ConnectionFactory.DEFAULT_HEARTBEAT) + public int requestedHeartbeat; + + /** + * Handshake timeout in milliseconds + */ + @ConfigItem(defaultValue = "" + ConnectionFactory.DEFAULT_HANDSHAKE_TIMEOUT) + public int handshakeTimeout; + + /** + * Shutdown timeout in milliseconds + */ + @ConfigItem(defaultValue = "" + ConnectionFactory.DEFAULT_SHUTDOWN_TIMEOUT) + public int shutdownTimeout; + + /** + * Maximum number of channels per connection + */ + @ConfigItem(defaultValue = "" + ConnectionFactory.DEFAULT_CHANNEL_MAX) + public int requestedChannelMax; + + /** + * Maximum frame size + */ + @ConfigItem(defaultValue = "" + ConnectionFactory.DEFAULT_FRAME_MAX) + public int requestedFrameMax; + + /** + * Network recovery interval in milliseconds + */ + @ConfigItem(defaultValue = "" + ConnectionFactory.DEFAULT_NETWORK_RECOVERY_INTERVAL) + public int networkRecoveryInterval; + + /** + * Channel RPC timeout in milliseconds + */ + @ConfigItem(defaultValue = "600000") + public int channelRpcTimeout; + + /** + * Validate channel RPC response type + */ + @ConfigItem(defaultValue = "false") + public boolean channelRpcResponseTypeCheck; + + /** + * Recover connection on failure + */ + @ConfigItem(defaultValue = "true") + public boolean connectionRecovery; + + /** + * Recover topology on failure + */ + @ConfigItem(defaultValue = "true") + public boolean topologyRecovery; + + /** + * Tls configuration + */ + @ConfigItem + @ConfigDocSection + public TlsConfig tls; + + /** + * Non blocking IO configuration + */ + @ConfigItem + @ConfigDocSection + public NioConfig nio; + + /** + * Client properties + */ + @ConfigItem + @ConfigDocMapKey("property-name") + @ConfigDocSection + public Map properties; + + @ConfigGroup + public static class Address { + + /** + * Hostname for connecting + */ + @ConfigItem + public String hostname; + + /** + * Port number for connecting + */ + @ConfigItem + public int port; + + } + + @ConfigGroup + public static class NioConfig { + + /** + * Enables non blocking IO + */ + @ConfigItem(defaultValue = "false") + public boolean enabled; + + /** + * Read buffer size in bytes + */ + @ConfigItem(defaultValue = "32768") + public int readByteBufferSize; + + /** + * Write buffer size in bytes + */ + @ConfigItem(defaultValue = "32768") + public int writeByteBufferSize; + + /** + * Number of non blocking IO threads + */ + @ConfigItem(defaultValue = "1") + public int threads; + + /** + * Write enqueuing timeout in milliseconds + */ + @ConfigItem(defaultValue = "10000") + public int writeEnqueuingTimeout; + + /** + * Write queue capacity. + */ + @ConfigItem(defaultValue = "10000") + public int writeQueueCapacity; + + } + + @ConfigGroup + public static class TlsConfig { + + /** + * Enables TLS + */ + @ConfigItem(defaultValue = "false") + public boolean enabled; + + /** + * TLS Algorithm to use + */ + @ConfigItem(defaultValue = "TLSv1.2") + public String algorithm; + + /** + * Trust store file + */ + @ConfigItem + public Optional trustStoreFile; + + /** + * Trust store type + */ + @ConfigItem(defaultValue = "JKS") + public String trustStoreType; + + /** + * Trust store algorithm + */ + @ConfigItem(defaultValue = "SunX509") + public String trustStoreAlgorithm; + + /** + * Trust store password + */ + @ConfigItem + public Optional trustStorePassword; + + /** + * Key store file + */ + @ConfigItem + public Optional keyStoreFile; + + /** + * Key store password + */ + @ConfigItem + public Optional keyStorePassword; + + /** + * Key store type + */ + @ConfigItem(defaultValue = "PKCS12") + public String keyStoreType; + + /** + * Key store algorithm + */ + @ConfigItem(defaultValue = "SunX509") + public String keyStoreAlgorithm; + + /** + * Validate server certificate + */ + @ConfigItem(defaultValue = "true") + public boolean validateServerCertificate; + + /** + * Verify hostname + */ + @ConfigItem(defaultValue = "true") + public boolean verifyHostname; + + } +} diff --git a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientException.java b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientException.java new file mode 100644 index 0000000..64281ad --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientException.java @@ -0,0 +1,35 @@ +package io.quarkiverse.rabbitmqclient; + +public class RabbitMQClientException extends RuntimeException { + + /** + * Constructs a new RabbitMQ client exception with the specified detail message. + * The cause is not initialized, and may subsequently be initialized by a + * call to {@link #initCause}. + * + * @param message the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method. + */ + public RabbitMQClientException(String message) { + super(message); + } + + /** + * Constructs a new RabbitMQ client exception with the specified detail message and + * cause. + *

+ * Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this runtime exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A {@code null} value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + */ + public RabbitMQClientException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientImpl.java b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientImpl.java new file mode 100644 index 0000000..5799c0d --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientImpl.java @@ -0,0 +1,74 @@ +package io.quarkiverse.rabbitmqclient; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.rabbitmq.client.AlreadyClosedException; +import com.rabbitmq.client.Connection; + +import io.quarkus.runtime.TlsConfig; + +/** + * RabbitMQ client implementation for {@link RabbitMQClient} + * + * @author b.passon + */ +class RabbitMQClientImpl implements RabbitMQClient { + + private static final Logger log = LoggerFactory.getLogger(RabbitMQClientImpl.class); + + private final RabbitMQClientConfig config; + private final TlsConfig tlsConfig; + private final Map connections; + + RabbitMQClientImpl(RabbitMQClientConfig config, TlsConfig tlsConfig) { + this.config = config; + this.tlsConfig = tlsConfig; + this.connections = new HashMap<>(); + } + + /** + * {@inheritDoc} + */ + @Override + public Connection connect() { + return connect(UUID.randomUUID().toString()); + } + + /** + * {@inheritDoc} + */ + @Override + public Connection connect(String name) { + log.debug("Opening connection {} with a RabbitMQ broker. Configured brokers: {}", name, + RabbitMQHelper.resolveBrokerAddresses(config)); + return this.connections.computeIfAbsent(name, n -> RabbitMQHelper.newConnection(config, tlsConfig, n)); + } + + /** + * {@inheritDoc} + */ + @Override + public void disconnect() { + connections.forEach((name, connection) -> { + try { + log.debug("Closing connection {} with RabbitMQ broker.", name); + // This will close all channels related to this connection + if (connection != null) { + connection.close(); + } + log.debug("Closed connection {} with RabbitMQ broker.", name); + } catch (AlreadyClosedException ex) { + log.debug("Already closed connection {} with RabbitMQ broker.", name); + } catch (IOException e) { + log.debug("Failed to close connection {} with RabbitMQ broker, ignoring.", name); + } + }); + + } +} diff --git a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientProducer.java b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientProducer.java new file mode 100644 index 0000000..7fdcb77 --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientProducer.java @@ -0,0 +1,44 @@ +package io.quarkiverse.rabbitmqclient; + +import javax.annotation.PreDestroy; +import javax.enterprise.inject.Produces; +import javax.inject.Singleton; + +import io.quarkus.arc.DefaultBean; +import io.quarkus.runtime.TlsConfig; + +/** + * Producer class for creating in injecting a {@link RabbitMQClient} instance. + * + * @author b.passon + */ +@Singleton +public class RabbitMQClientProducer { + + private RabbitMQClientImpl rabbitMQClient; + + /** + * Creates a singleton {@link RabbitMQClient}. + * + * @param config the {@link RabbitMQClientConfig} to use. + * @param tlsConfig the {@link TlsConfig} to use. + * @return a configured {@link RabbitMQClient}. + */ + @DefaultBean + @Singleton + @Produces + public RabbitMQClient rabbitMQClient(RabbitMQClientConfig config, TlsConfig tlsConfig) { + if (rabbitMQClient == null) { + rabbitMQClient = new RabbitMQClientImpl(config, tlsConfig); + } + return rabbitMQClient; + } + + /** + * Destroys the {@link RabbitMQClient} and closes all connections. + */ + @PreDestroy + public void destroy() { + rabbitMQClient.disconnect(); + } +} diff --git a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQHelper.java b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQHelper.java new file mode 100644 index 0000000..3bf6061 --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQHelper.java @@ -0,0 +1,174 @@ +package io.quarkiverse.rabbitmqclient; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; + +import com.rabbitmq.client.Address; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.ConnectionFactoryConfigurator; + +import io.quarkus.runtime.TlsConfig; + +/** + * Helper class with RabbitMQClient utility methods. + * + * @author b.passon + */ +class RabbitMQHelper { + + static final String CLIENT_PROPERTY_PREFIX = "client-property."; + + /** + * Opens a new connection to a RabbitMQ broker using the given configuration and name. + * + * @param config the {@link RabbitMQClientConfig}. + * @param tlsConfig the {@link TlsConfig}. + * @param name the name for the connection. + * @return a {@link Connection} connected to a configured RabbitMQ broker. + * @throws RabbitMQClientException if a failure occurs. + */ + public static Connection newConnection(RabbitMQClientConfig config, TlsConfig tlsConfig, String name) { + try { + ConnectionFactory cf = newConnectionFactory(config, tlsConfig); + List

addresses = config.addresses.isEmpty() + ? Collections.singletonList(new Address(config.hostname, config.port)) + : convertAddresses(config.addresses); + + return addresses == null ? cf.newConnection(name) : cf.newConnection(addresses, name); + } catch (Exception e) { + throw new RabbitMQClientException("Failed to connect to RabbitMQ broker", e); + } + } + + private static ConnectionFactory newConnectionFactory(RabbitMQClientConfig config, TlsConfig tlsConfig) { + ConnectionFactory cf = new ConnectionFactory(); + ConnectionFactoryConfigurator.load(cf, newProperties(config, tlsConfig), ""); + + String uri = config.uri.orElse(null); + if (uri != null) { + try { + cleanUriConnectionProperties(cf); + cf.setUri(uri); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid RabbitMQ connection URI " + uri); + } + } + + return cf; + } + + /** + * Resolves the available broker addresses based on the given configuration. + * + * @param config the {@link RabbitMQClientConfig}. + * @return a list of RabbitMQ broker addresses. + */ + public static List
resolveBrokerAddresses(RabbitMQClientConfig config) { + return config.addresses.isEmpty() + ? Collections.singletonList(new Address(config.hostname, config.port)) + : convertAddresses(config.addresses); + } + + private static List
convertAddresses(Map addresses) { + return addresses.values() + .stream() + .map(a -> new Address(a.hostname, a.port)) + .collect(Collectors.toList()); + } + + private static void cleanUriConnectionProperties(ConnectionFactory cf) { + cf.setUsername(null); + cf.setPassword(null); + cf.setHost(null); + cf.setPort(-1); + cf.setVirtualHost(null); + } + + /** + * Compute the {@link Properties} for use with {@link ConnectionFactoryConfigurator}. + * + * @param config the {@link RabbitMQClient} config. + * @param tlsConfig the Quarkus generic {@link TlsConfig}. + * @return the computed properties. + */ + static Properties newProperties(RabbitMQClientConfig config, TlsConfig tlsConfig) { + Properties properties = new Properties(); + properties.setProperty(ConnectionFactoryConfigurator.USERNAME, config.username); + properties.setProperty(ConnectionFactoryConfigurator.PASSWORD, config.password); + properties.setProperty(ConnectionFactoryConfigurator.VIRTUAL_HOST, config.virtualHost); + properties.setProperty(ConnectionFactoryConfigurator.HOST, config.hostname); + properties.setProperty(ConnectionFactoryConfigurator.PORT, Integer.toString(config.port)); + properties.setProperty(ConnectionFactoryConfigurator.CONNECTION_CHANNEL_MAX, + Integer.toString(config.requestedChannelMax)); + properties.setProperty(ConnectionFactoryConfigurator.CONNECTION_FRAME_MAX, Integer.toString(config.requestedFrameMax)); + properties.setProperty(ConnectionFactoryConfigurator.CONNECTION_HEARTBEAT, Integer.toString(config.requestedHeartbeat)); + properties.setProperty(ConnectionFactoryConfigurator.CONNECTION_TIMEOUT, Integer.toString(config.connectionTimeout)); + properties.setProperty(ConnectionFactoryConfigurator.HANDSHAKE_TIMEOUT, Integer.toString(config.handshakeTimeout)); + properties.setProperty(ConnectionFactoryConfigurator.SHUTDOWN_TIMEOUT, Integer.toString(config.shutdownTimeout)); + + // client properties + properties.setProperty(ConnectionFactoryConfigurator.CLIENT_PROPERTIES_PREFIX, CLIENT_PROPERTY_PREFIX); + config.properties.forEach((name, value) -> { + properties.setProperty(CLIENT_PROPERTY_PREFIX + name, value); + }); + + properties.setProperty(ConnectionFactoryConfigurator.CONNECTION_RECOVERY_ENABLED, + Boolean.toString(config.connectionRecovery)); + properties.setProperty(ConnectionFactoryConfigurator.TOPOLOGY_RECOVERY_ENABLED, + Boolean.toString(config.topologyRecovery)); + properties.setProperty(ConnectionFactoryConfigurator.CONNECTION_RECOVERY_INTERVAL, + Integer.toString(config.networkRecoveryInterval)); + properties.setProperty(ConnectionFactoryConfigurator.CHANNEL_RPC_TIMEOUT, Integer.toString(config.channelRpcTimeout)); + properties.setProperty(ConnectionFactoryConfigurator.CHANNEL_SHOULD_CHECK_RPC_RESPONSE_TYPE, + Boolean.toString(config.channelRpcResponseTypeCheck)); + + // NIO + properties.setProperty(ConnectionFactoryConfigurator.USE_NIO, Boolean.toString(config.nio.enabled)); + properties.setProperty(ConnectionFactoryConfigurator.NIO_READ_BYTE_BUFFER_SIZE, + Integer.toString(config.nio.readByteBufferSize)); + properties.setProperty(ConnectionFactoryConfigurator.NIO_WRITE_BYTE_BUFFER_SIZE, + Integer.toString(config.nio.writeByteBufferSize)); + properties.setProperty(ConnectionFactoryConfigurator.NIO_NB_IO_THREADS, Integer.toString(config.nio.threads)); + properties.setProperty(ConnectionFactoryConfigurator.NIO_WRITE_ENQUEUING_TIMEOUT_IN_MS, + Integer.toString(config.nio.writeEnqueuingTimeout)); + properties.setProperty(ConnectionFactoryConfigurator.NIO_WRITE_QUEUE_CAPACITY, + Integer.toString(config.nio.writeQueueCapacity)); + + // TLS Configuration + if (config.tls != null) { + properties.setProperty(ConnectionFactoryConfigurator.SSL_ALGORITHM, config.tls.algorithm); + properties.setProperty(ConnectionFactoryConfigurator.SSL_ENABLED, Boolean.toString(config.tls.enabled)); + + if (tlsConfig.trustAll) { + properties.setProperty(ConnectionFactoryConfigurator.SSL_VALIDATE_SERVER_CERTIFICATE, Boolean.FALSE.toString()); + properties.setProperty(ConnectionFactoryConfigurator.SSL_VERIFY_HOSTNAME, Boolean.FALSE.toString()); + } else { + properties.setProperty(ConnectionFactoryConfigurator.SSL_VALIDATE_SERVER_CERTIFICATE, + Boolean.toString(config.tls.validateServerCertificate)); + properties.setProperty(ConnectionFactoryConfigurator.SSL_VERIFY_HOSTNAME, + Boolean.toString(config.tls.verifyHostname)); + + // TLS Keys + config.tls.keyStoreFile.ifPresent(s -> properties.setProperty(ConnectionFactoryConfigurator.SSL_KEY_STORE, s)); + config.tls.keyStorePassword + .ifPresent(s -> properties.setProperty(ConnectionFactoryConfigurator.SSL_KEY_STORE_PASSWORD, s)); + properties.setProperty(ConnectionFactoryConfigurator.SSL_KEY_STORE_TYPE, config.tls.keyStoreType); + properties.setProperty(ConnectionFactoryConfigurator.SSL_KEY_STORE_ALGORITHM, config.tls.keyStoreAlgorithm); + + // TLS Trust + config.tls.trustStoreFile + .ifPresent(s -> properties.setProperty(ConnectionFactoryConfigurator.SSL_TRUST_STORE, s)); + config.tls.trustStorePassword + .ifPresent(s -> properties.setProperty(ConnectionFactoryConfigurator.SSL_TRUST_STORE_PASSWORD, s)); + properties.setProperty(ConnectionFactoryConfigurator.SSL_TRUST_STORE_TYPE, config.tls.trustStoreType); + properties.setProperty(ConnectionFactoryConfigurator.SSL_TRUST_STORE_ALGORITHM, config.tls.trustStoreAlgorithm); + } + } + + return properties; + } +} diff --git a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQReadyCheck.java b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQReadyCheck.java new file mode 100644 index 0000000..a54da8e --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQReadyCheck.java @@ -0,0 +1,48 @@ +package io.quarkiverse.rabbitmqclient; + +import java.net.Socket; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.Readiness; + +import com.rabbitmq.client.Address; + +/** + * RabbitMQ ready check which checks if at least one of the configured brokers is available. + * + * @author b.passon + */ +@Readiness +@ApplicationScoped +public class RabbitMQReadyCheck implements HealthCheck { + + @Inject + RabbitMQClientConfig config; + + @Override + public HealthCheckResponse call() { + if (atLeastOneBrokerIsAlive()) { + return HealthCheckResponse.up("At least one RabbitMQ broker is available."); + } + return HealthCheckResponse.down("No RabbitMQ broker is available."); + } + + private boolean atLeastOneBrokerIsAlive() { + return RabbitMQHelper.resolveBrokerAddresses(config) + .stream() + .anyMatch(this::isBrokerAvailable); + } + + private boolean isBrokerAvailable(Address address) { + try { + new Socket(address.getHost(), address.getPort()); + return true; + } catch (Exception e) { + return false; + } + } +} From 6c78dd0e6ceb888460e10e73e77a7dfd44453884 Mon Sep 17 00:00:00 2001 From: Bas Passon Date: Wed, 27 Jan 2021 21:28:22 +0100 Subject: [PATCH 2/8] Updated documentation --- docs/antora.yml | 6 +++--- docs/modules/ROOT/nav.adoc | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/antora.yml b/docs/antora.yml index e33b9c0..44db062 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -1,5 +1,5 @@ -name: quarkiverse-project -title: Quarkiverse -version: dev +name: quarkus-rabbitmq-client +title: Quarkus RabbitMQ Client +version: 0.1.0 nav: - modules/ROOT/nav.adoc diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index c00ba3c..bf5d49f 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -1 +1 @@ -* xref:index.adoc[Quarkiverse ] +* xref:index.adoc[Quarkiverse RabbitMQ Client] From c9fc27b39b2b575e107bdc6e809737ab419ac3fd Mon Sep 17 00:00:00 2001 From: Bas Passon Date: Wed, 27 Jan 2021 21:56:12 +0100 Subject: [PATCH 3/8] Updated how to include the extension --- README.md | 2 +- docs/modules/ROOT/pages/index.adoc | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d3b94c8..5e7067f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ RabbitMQ is a popular message broker. This Quarkus extension provides a client f io.quarkiverse.rabbitmqclient quarkus-rabbitmq-client - LATEST + 0.1.0 ``` diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index 14aee87..32522d0 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -13,6 +13,7 @@ In your `pom.xml` file, add: io.quarkiverse quarkus-rabbitmq-client + 0.1.0 ---- From 95e891d82e3a0dd21c43d545c4bb34efbdef5113 Mon Sep 17 00:00:00 2001 From: Bas Passon Date: Wed, 27 Jan 2021 22:15:02 +0100 Subject: [PATCH 4/8] Updated project.yml --- .github/project.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/project.yml b/.github/project.yml index 90f4f23..316d19a 100644 --- a/.github/project.yml +++ b/.github/project.yml @@ -1,5 +1,5 @@ -name: Quarkiverse Extension +name: Quarkus RabbitMQ Client Extension release: - current-version: 0 - next-version: 0.0.1-SNAPSHOT + current-version: 0.1.0 + next-version: 0.2.0-SNAPSHOT From 732a3a457ceefb0e8f4b63ee8b08af542841030d Mon Sep 17 00:00:00 2001 From: Bas Passon Date: Mon, 1 Feb 2021 09:45:18 +0100 Subject: [PATCH 5/8] Resolved PR comments, added RabbitMQ test container to test SSL connections --- LICENSE | 2 +- deployment/pom.xml | 63 ++++++++++++ deployment/scripts/bootstrap-ca.sh | 98 +++++++++++++++++++ .../QuarkusRabbitmqClientProcessor.java | 14 ++- .../QuarkusRabbitMQConnectionTest.java | 64 ++++++++++++ .../QuarkusRabbitmqReadyCheckTest.java | 73 ++++++++++++-- .../{ => util}/DummyServer.java | 19 +++- .../util/RabbitMQTestContainer.java | 42 ++++++++ .../rabbitmqclient/util/TestConfig.java | 55 +++++++++++ .../resources/empty-properties.properties | 0 .../ready-check-properties.properties | 12 --- .../testcontainer/rabbitmq/rabbit.conf | 22 +++++ .../rabbitmq/rabbitmq-properties.properties | 3 + pom.xml | 2 + .../RabbitMQClientBuildConfig.java | 5 + .../rabbitmqclient/RabbitMQClientConfig.java | 11 ++- .../RabbitMQClientException.java | 17 +--- .../RabbitMQClientProducer.java | 12 ++- .../rabbitmqclient/RabbitMQHelper.java | 9 +- .../rabbitmqclient/RabbitMQReadyCheck.java | 31 ++++-- 20 files changed, 494 insertions(+), 60 deletions(-) create mode 100755 deployment/scripts/bootstrap-ca.sh create mode 100644 deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitMQConnectionTest.java rename deployment/src/test/java/io/quarkiverse/rabbitmqclient/{ => util}/DummyServer.java (83%) create mode 100644 deployment/src/test/java/io/quarkiverse/rabbitmqclient/util/RabbitMQTestContainer.java create mode 100644 deployment/src/test/java/io/quarkiverse/rabbitmqclient/util/TestConfig.java create mode 100644 deployment/src/test/resources/empty-properties.properties delete mode 100644 deployment/src/test/resources/ready-check-properties.properties create mode 100644 deployment/src/test/resources/testcontainer/rabbitmq/rabbit.conf create mode 100644 deployment/src/test/resources/testcontainer/rabbitmq/rabbitmq-properties.properties diff --git a/LICENSE b/LICENSE index a3cc671..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2021 Quarkiverse.io + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/deployment/pom.xml b/deployment/pom.xml index d09d48a..2f15dc7 100644 --- a/deployment/pom.xml +++ b/deployment/pom.xml @@ -41,10 +41,73 @@ quarkus-smallrye-health test + + org.testcontainers + testcontainers + ${testcontainers.version} + test + + + org.testcontainers + junit-jupiter + ${testcontainers.version} + test + + + org.testcontainers + rabbitmq + ${testcontainers.version} + test + + + org.codehaus.mojo + exec-maven-plugin + ${exec-plugin.version} + + + generate-test-resources + + exec + + + + + ${project.basedir}/scripts/bootstrap-ca.sh + + ${project.basedir}/target/testcontainer/rabbitmq + + + + + org.apache.maven.plugins + maven-resources-plugin + + + process-test-resources + + + + + + ${project.basedir}/src/test/resources + + + ${project.basedir}/target + + testcontainer/rabbitmq/ca/cacert.pem + testcontainer/rabbitmq/server/cert.pem + testcontainer/rabbitmq/server/key.pem + testcontainer/rabbitmq/client/client.jks + testcontainer/rabbitmq/ca/cacerts.jks + + + + + org.apache.maven.plugins maven-compiler-plugin diff --git a/deployment/scripts/bootstrap-ca.sh b/deployment/scripts/bootstrap-ca.sh new file mode 100755 index 0000000..70a8441 --- /dev/null +++ b/deployment/scripts/bootstrap-ca.sh @@ -0,0 +1,98 @@ +#!/bin/bash +CLIENT_NAME=client +HOSTNAME=rabbitmq +TARGET_DIR=${1:-openssl} + +echo "[CA]: Bootstrapping CA directories ..." +rm -rf ${TARGET_DIR} +mkdir -p ${TARGET_DIR}/ca/certs +mkdir -p ${TARGET_DIR}/ca/private +chmod 0750 ${TARGET_DIR}/ca/private +echo 01 > ${TARGET_DIR}/ca/serial +touch ${TARGET_DIR}/ca/index.txt +mkdir -p ${TARGET_DIR}/server +mkdir -p ${TARGET_DIR}/client + +echo "[CA]: Setting up openssl configuration ..." +cat < ${TARGET_DIR}/ca/openssl.cnf +[ ca ] +default_ca = ${HOSTNAME} + +[ ${HOSTNAME} ] +dir = ${TARGET_DIR}/ca +certificate = \$dir/cacert.pem +database = \$dir/index.txt +new_certs_dir = \$dir/certs +private_key = \$dir/private/cakey.pem +serial = \$dir/serial + +default_crl_days = 7 +default_days = 365 +default_md = sha256 + +policy = ${HOSTNAME}_policy +x509_extensions = certificate_extensions +copy_extensions = copy + +[ ${HOSTNAME}_policy ] +commonName = supplied +stateOrProvinceName = optional +countryName = optional +emailAddress = optional +organizationName = optional +organizationalUnitName = optional +domainComponent = optional + +[ certificate_extensions ] +basicConstraints = CA:false + +[ req ] +default_bits = 2048 +default_keyfile = ${TARGET_DIR}/ca/private/cakey.pem +default_md = sha256 +prompt = yes +distinguished_name = root_ca_distinguished_name +x509_extensions = root_ca_extensions + +[ root_ca_distinguished_name ] +commonName = ${HOSTNAME} + +[ root_ca_extensions ] +basicConstraints = CA:true +keyUsage = keyCertSign, cRLSign + +[ ${CLIENT_NAME}_ca_extensions ] +basicConstraints = CA:false +keyUsage = digitalSignature,keyEncipherment +extendedKeyUsage = 1.3.6.1.5.5.7.3.2 + +[ server_ca_extensions ] +basicConstraints = CA:false +keyUsage = digitalSignature,keyEncipherment +extendedKeyUsage = 1.3.6.1.5.5.7.3.1 +EOT + +echo $(pwd) +echo "[CA]: Generating CA certificate ..." +openssl req -x509 -config ${TARGET_DIR}/ca/openssl.cnf -newkey rsa:2048 -days 365 -out ${TARGET_DIR}/ca/cacert.pem -outform PEM -subj /CN=${HOSTNAME}/ -nodes +keytool -import -alias rabbitmqca -keystore ${TARGET_DIR}/ca/cacerts.jks -file ${TARGET_DIR}/ca/cacert.pem -storepass letmein -storetype JKS -noprompt + +echo "[SERVER]: Generating server keys and certficate ..." +openssl genrsa -out ${TARGET_DIR}/server/key.pem 2048 +openssl req -new -key ${TARGET_DIR}/server/key.pem -out ${TARGET_DIR}/server/req.pem -outform PEM -subj /CN=${HOSTNAME}/O=server/ -nodes \ + -reqexts SAN \ + -config <(cat ${TARGET_DIR}/ca/openssl.cnf \ + <(printf "\n[SAN]\nsubjectAltName=DNS:localhost")) + +openssl ca -config ${TARGET_DIR}/ca/openssl.cnf -in ${TARGET_DIR}/server/req.pem -out ${TARGET_DIR}/server/cert.pem -notext -batch -extensions server_ca_extensions +openssl pkcs12 -export -out ${TARGET_DIR}/server/keycert.p12 -in ${TARGET_DIR}/server/cert.pem -inkey ${TARGET_DIR}/server/key.pem -passout pass:letmein + +echo "[CLIENT]: Generating client keys and certificate ..." +openssl genrsa -out ${TARGET_DIR}/client/key.pem 2048 +openssl req -new -key ${TARGET_DIR}/client/key.pem -out ${TARGET_DIR}/client/req.pem -outform PEM -subj /CN=${CLIENT_NAME}/O=client/ -nodes +openssl ca -config ${TARGET_DIR}/ca/openssl.cnf -in ${TARGET_DIR}/client/req.pem -out ${TARGET_DIR}/client/cert.pem -notext -batch -extensions client_ca_extensions +openssl pkcs12 -export -out ${TARGET_DIR}/client/keycert.p12 -in ${TARGET_DIR}/client/cert.pem -inkey ${TARGET_DIR}/client/key.pem -passout pass:letmein +keytool -importkeystore -deststorepass letmein -destkeypass letmein -destkeystore ${TARGET_DIR}/client/client.jks -srckeystore ${TARGET_DIR}/client/keycert.p12 \ + -srcstoretype PKCS12 -srcstorepass letmein -alias 1 + +echo "Done ..." diff --git a/deployment/src/main/java/io/quarkiverse/rabbitmqclient/deployment/QuarkusRabbitmqClientProcessor.java b/deployment/src/main/java/io/quarkiverse/rabbitmqclient/deployment/QuarkusRabbitmqClientProcessor.java index 9d7031e..e38e324 100644 --- a/deployment/src/main/java/io/quarkiverse/rabbitmqclient/deployment/QuarkusRabbitmqClientProcessor.java +++ b/deployment/src/main/java/io/quarkiverse/rabbitmqclient/deployment/QuarkusRabbitmqClientProcessor.java @@ -6,9 +6,13 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; -import io.quarkus.deployment.builditem.IndexDependencyBuildItem; import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; +/** + * RabbitMQ client processor. + * + * @author b.passon + */ class QuarkusRabbitmqClientProcessor { private static final String FEATURE = "rabbitmq-client"; @@ -18,10 +22,10 @@ FeatureBuildItem feature() { return new FeatureBuildItem(FEATURE); } - @BuildStep - void addDependencies(BuildProducer indexDependency) { - indexDependency.produce(new IndexDependencyBuildItem("com.rabbitmq", "amqp-client")); - } + // @BuildStep + // void addDependencies(BuildProducer indexDependency) { + // indexDependency.produce(new IndexDependencyBuildItem("com.rabbitmq", "amqp-client")); + // } @BuildStep HealthBuildItem addHealthCheck(RabbitMQClientBuildConfig buildTimeConfig) { diff --git a/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitMQConnectionTest.java b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitMQConnectionTest.java new file mode 100644 index 0000000..52940e1 --- /dev/null +++ b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitMQConnectionTest.java @@ -0,0 +1,64 @@ +package io.quarkiverse.rabbitmqclient; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.rabbitmq.client.Connection; + +import io.quarkiverse.rabbitmqclient.util.RabbitMQTestContainer; +import io.quarkiverse.rabbitmqclient.util.TestConfig; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.QuarkusTestResource; + +@QuarkusTestResource(RabbitMQTestContainer.class) +public class QuarkusRabbitMQConnectionTest extends RabbitMQConfigTest { + + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() // Start unit test with your extension loaded + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(TestConfig.class) + .addAsResource( + QuarkusRabbitMQConnectionTest.class + .getResource("/testcontainer/rabbitmq/rabbitmq-properties.properties"), + "application.properties") + .addAsResource(QuarkusRabbitMQConnectionTest.class.getResource("/testcontainer/rabbitmq/ca/cacerts.jks"), + "rabbitmq/ca/cacerts.jks") + .addAsResource(QuarkusRabbitMQConnectionTest.class.getResource("/testcontainer/rabbitmq/client/client.jks"), + "rabbitmq/client/client.jks")); + + @Inject + RabbitMQClientConfig config; + + @Inject + TestConfig testConfig; + + @Inject + RabbitMQClient rabbitMQClient; + + @Test + public void testNonSSL() { + testConfig.setupNonSll(config); + Connection conn = rabbitMQClient.connect("test-connection-non-ssl"); + Assertions.assertNotNull(conn); + } + + @Test + public void testRabbitMQSSLDefault() { + testConfig.setupBasicSsl(config); + Connection conn = rabbitMQClient.connect("test-connection-ssl"); + Assertions.assertNotNull(conn); + } + + @Test + public void testRabbitMQSSLClientCert() { + testConfig.setupClientCertSsl(config); + Connection conn = rabbitMQClient.connect("test-connection-ssl-client-cert"); + Assertions.assertNotNull(conn); + } + +} diff --git a/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqReadyCheckTest.java b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqReadyCheckTest.java index 2b552e7..04b66f4 100644 --- a/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqReadyCheckTest.java +++ b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/QuarkusRabbitmqReadyCheckTest.java @@ -1,5 +1,8 @@ package io.quarkiverse.rabbitmqclient; +import java.util.ArrayList; +import java.util.List; + import javax.inject.Inject; import org.eclipse.microprofile.health.HealthCheckResponse; @@ -12,15 +15,19 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.quarkiverse.rabbitmqclient.util.DummyServer; +import io.quarkiverse.rabbitmqclient.util.RabbitMQTestContainer; import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.QuarkusTestResource; +@QuarkusTestResource(RabbitMQTestContainer.class) public class QuarkusRabbitmqReadyCheckTest { @RegisterExtension static final QuarkusUnitTest unitTest = new QuarkusUnitTest() // Start unit test with your extension loaded .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) .addClasses(DummyServer.class) - .addAsResource(QuarkusRabbitmqReadyCheckTest.class.getResource("/ready-check-properties.properties"), + .addAsResource(QuarkusRabbitmqReadyCheckTest.class.getResource("/empty-properties.properties"), "application.properties")); @Inject @@ -30,29 +37,75 @@ public class QuarkusRabbitmqReadyCheckTest { @Inject RabbitMQReadyCheck readyCheck; - private DummyServer dummyServer; + private List dummyServers; @BeforeEach - public void before() { - dummyServer = DummyServer.newDummyServer(); - config.port = dummyServer.getPort(); + public void setup() { + dummyServers = new ArrayList<>(); } @AfterEach - public void after() { - dummyServer.close(); + public void cleanup() { + dummyServers.forEach(DummyServer::close); } @Test - public void testHealthEndpointUp() { + public void testHealthEndpointAllBrokersUp() { + setupDummyServers(config, 2, 0); HealthCheckResponse resp = readyCheck.call(); Assertions.assertEquals(HealthCheckResponse.State.UP, resp.getState()); + assertNumberOfBrokersInState(resp, 2, HealthCheckResponse.State.UP); + assertData(resp); + } + + @Test + public void testHealthEndpointOneBrokerDown() { + setupDummyServers(config, 3, 1); + HealthCheckResponse resp = readyCheck.call(); + Assertions.assertEquals(HealthCheckResponse.State.DOWN, resp.getState()); + assertNumberOfBrokersInState(resp, 2, HealthCheckResponse.State.UP); + assertNumberOfBrokersInState(resp, 1, HealthCheckResponse.State.DOWN); + assertData(resp); } @Test - public void testHealthEndpointDown() { - dummyServer.close(); + public void testHealthEndpointAllBrokersDown() { + setupDummyServers(config, 5, 5); HealthCheckResponse resp = readyCheck.call(); Assertions.assertEquals(HealthCheckResponse.State.DOWN, resp.getState()); + assertNumberOfBrokersInState(resp, 0, HealthCheckResponse.State.UP); + assertNumberOfBrokersInState(resp, 5, HealthCheckResponse.State.DOWN); + assertData(resp); + } + + private void setupDummyServers(RabbitMQClientConfig config, int number, int down) { + config.addresses.clear(); + for (int i = 0; i < number; i++) { + DummyServer ds = DummyServer.newDummyServer(); + RabbitMQClientConfig.Address address = new RabbitMQClientConfig.Address(); + address.hostname = ds.getHostname(); + address.port = ds.getPort(); + config.addresses.put("dummy-" + i, address); + dummyServers.add(ds); + if (i < down) { + ds.close(); + } + } + } + + private void assertNumberOfBrokersInState(HealthCheckResponse resp, int number, HealthCheckResponse.State state) { + Assertions.assertTrue(resp.getData().isPresent()); + Assertions.assertEquals(number, resp.getData().get().values().stream().filter(s -> s.equals(state.name())).count()); + } + + private void assertData(HealthCheckResponse resp) { + Assertions.assertTrue(resp.getData().isPresent()); + Assertions.assertEquals(dummyServers.size(), resp.getData().get().size()); + dummyServers.forEach(ds -> { + Object obj = resp.getData().get().get(ds.toString()); + Assertions.assertNotNull(obj); + Assertions.assertEquals(obj.toString(), + ds.isAvailable() ? HealthCheckResponse.State.UP.name() : HealthCheckResponse.State.DOWN.name()); + }); } } diff --git a/deployment/src/test/java/io/quarkiverse/rabbitmqclient/DummyServer.java b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/util/DummyServer.java similarity index 83% rename from deployment/src/test/java/io/quarkiverse/rabbitmqclient/DummyServer.java rename to deployment/src/test/java/io/quarkiverse/rabbitmqclient/util/DummyServer.java index ac38dbe..1d973ca 100644 --- a/deployment/src/test/java/io/quarkiverse/rabbitmqclient/DummyServer.java +++ b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/util/DummyServer.java @@ -1,4 +1,4 @@ -package io.quarkiverse.rabbitmqclient; +package io.quarkiverse.rabbitmqclient.util; import java.io.IOException; import java.io.UncheckedIOException; @@ -8,7 +8,7 @@ import javax.net.ServerSocketFactory; -class DummyServer { +public class DummyServer { public static final int PORT_RANGE_MIN = 1025; public static final int PORT_RANGE_MAX = 65535; @@ -16,13 +16,27 @@ class DummyServer { private ServerSocket socket = null; private int port; + private boolean available; public int getPort() { return port; } + public String getHostname() { + return "localhost"; + } + + public boolean isAvailable() { + return available; + } + + public String toString() { + return "localhost:" + port; + } + public void close() { try { + available = false; socket.close(); } catch (IOException ioe) { throw new UncheckedIOException(ioe); @@ -34,6 +48,7 @@ public static DummyServer newDummyServer() { DummyServer ds = new DummyServer(); ds.port = findFreePort(); ds.socket = ServerSocketFactory.getDefault().createServerSocket(ds.port, 50, InetAddress.getByName("localhost")); + ds.available = true; return ds; } catch (IOException ioe) { throw new UncheckedIOException(ioe); diff --git a/deployment/src/test/java/io/quarkiverse/rabbitmqclient/util/RabbitMQTestContainer.java b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/util/RabbitMQTestContainer.java new file mode 100644 index 0000000..2cf3724 --- /dev/null +++ b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/util/RabbitMQTestContainer.java @@ -0,0 +1,42 @@ +package io.quarkiverse.rabbitmqclient.util; + +import java.util.HashMap; +import java.util.Map; + +import org.testcontainers.containers.RabbitMQContainer; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; + +public class RabbitMQTestContainer implements QuarkusTestResourceLifecycleManager { + + private static final String AMQPS_PORT = "test.amqps-port"; + private static final String AMQP_PORT = "test.amqp-port"; + + private final MountableFile keyFile = MountableFile.forClasspathResource("/testcontainer/rabbitmq/server/key.pem"); + private final MountableFile certFile = MountableFile.forClasspathResource("/testcontainer/rabbitmq/server/cert.pem"); + private final MountableFile caFile = MountableFile.forClasspathResource("/testcontainer/rabbitmq/ca/cacert.pem"); + private final MountableFile configFile = MountableFile.forClasspathResource("/testcontainer/rabbitmq/rabbit.conf"); + private RabbitMQContainer rabbitmq; + + @Override + public Map start() { + rabbitmq = new RabbitMQContainer(DockerImageName.parse("rabbitmq:3.8.11-management-alpine")) + .withCopyFileToContainer(keyFile, "/etc/rabbitmq/rabbitmq_key.pem") + .withCopyFileToContainer(certFile, "/etc/rabbitmq/rabbitmq_cert.pem") + .withCopyFileToContainer(caFile, "/etc/rabbitmq/ca_cert.pem") + .withRabbitMQConfig(configFile); + + rabbitmq.start(); + Map testConfig = new HashMap<>(); + testConfig.put(AMQP_PORT, rabbitmq.getAmqpPort().toString()); + testConfig.put(AMQPS_PORT, rabbitmq.getAmqpsPort().toString()); + return testConfig; + } + + @Override + public void stop() { + rabbitmq.close(); + } +} diff --git a/deployment/src/test/java/io/quarkiverse/rabbitmqclient/util/TestConfig.java b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/util/TestConfig.java new file mode 100644 index 0000000..e72389f --- /dev/null +++ b/deployment/src/test/java/io/quarkiverse/rabbitmqclient/util/TestConfig.java @@ -0,0 +1,55 @@ +package io.quarkiverse.rabbitmqclient.util; + +import java.util.Optional; + +import io.quarkiverse.rabbitmqclient.RabbitMQClientConfig; +import io.quarkus.arc.config.ConfigProperties; +import io.quarkus.runtime.annotations.ConfigItem; + +@ConfigProperties(prefix = "test") +public class TestConfig { + + @ConfigItem + public int amqpsPort; + + @ConfigItem + public int amqpPort; + + public void setupNonSll(RabbitMQClientConfig config) { + config.port = amqpPort; + config.virtualHost = "/"; + config.username = "guest"; + config.password = "guest"; + config.hostname = "localhost"; + config.tls.enabled = false; + } + + public void setupBasicSsl(RabbitMQClientConfig config) { + config.port = amqpsPort; + config.virtualHost = "/"; + config.username = "guest"; + config.password = "guest"; + config.hostname = "localhost"; + config.tls.enabled = true; + config.tls.keyStoreFile = Optional.empty(); + config.tls.keyStorePassword = Optional.empty(); + config.tls.trustStoreFile = Optional.of("classpath:/rabbitmq/ca/cacerts.jks"); + config.tls.trustStoreType = "JKS"; + config.tls.trustStorePassword = Optional.of("letmein"); + } + + public void setupClientCertSsl(RabbitMQClientConfig config) { + config.port = amqpsPort; + config.virtualHost = "/"; + config.username = "guest"; + config.password = "guest"; + config.hostname = "localhost"; + config.tls.enabled = true; + config.tls.keyStoreFile = Optional.of("classpath:/rabbitmq/client/client.jks"); + config.tls.keyStoreType = "JKS"; + config.tls.keyStorePassword = Optional.of("letmein"); + config.tls.trustStoreFile = Optional.of("classpath:/rabbitmq/ca/cacerts.jks"); + config.tls.trustStoreType = "JKS"; + config.tls.trustStorePassword = Optional.of("letmein"); + } +} diff --git a/deployment/src/test/resources/empty-properties.properties b/deployment/src/test/resources/empty-properties.properties new file mode 100644 index 0000000..e69de29 diff --git a/deployment/src/test/resources/ready-check-properties.properties b/deployment/src/test/resources/ready-check-properties.properties deleted file mode 100644 index 84bb6c3..0000000 --- a/deployment/src/test/resources/ready-check-properties.properties +++ /dev/null @@ -1,12 +0,0 @@ -quarkus.log.level=INFO -quarkus.log.category."io.quarkiverse.rabbitmqclient".min-level=DEBUG -quarkus.log.category."io.quarkiverse.rabbitmqclient".level=DEBUG - -quarkus.http.test-port=34567 - -#RabbitMQ -quarkus.rabbitmqclient.virtual-host=/vhost -quarkus.rabbitmqclient.username=someuser -quarkus.rabbitmqclient.password=somepass -quarkus.rabbitmqclient.hostname=localhost -quarkus.rabbitmqclient.port=5672 \ No newline at end of file diff --git a/deployment/src/test/resources/testcontainer/rabbitmq/rabbit.conf b/deployment/src/test/resources/testcontainer/rabbitmq/rabbit.conf new file mode 100644 index 0000000..e76b94e --- /dev/null +++ b/deployment/src/test/resources/testcontainer/rabbitmq/rabbit.conf @@ -0,0 +1,22 @@ +loopback_users.guest = false +log.console.level = debug + +#service +listeners.tcp.default = 5672 +listeners.ssl.default = 5671 +ssl_options.cacertfile = /etc/rabbitmq/ca_cert.pem +ssl_options.certfile = /etc/rabbitmq/rabbitmq_cert.pem +ssl_options.fail_if_no_peer_cert = false +ssl_options.keyfile = /etc/rabbitmq/rabbitmq_key.pem +ssl_options.verify = verify_peer +ssl_options.versions.1 = tlsv1.2 +ssl_options.versions.2 = tlsv1.1 + +#management +management.tcp.port = 15672 +management.ssl.port = 15671 +management.ssl.cacertfile = /etc/rabbitmq/ca_cert.pem +management.ssl.certfile = /etc/rabbitmq/rabbitmq_cert.pem +management.ssl.fail_if_no_peer_cert = false +management.ssl.keyfile = /etc/rabbitmq/rabbitmq_key.pem +management.ssl.verify = verify_none diff --git a/deployment/src/test/resources/testcontainer/rabbitmq/rabbitmq-properties.properties b/deployment/src/test/resources/testcontainer/rabbitmq/rabbitmq-properties.properties new file mode 100644 index 0000000..13eb6a5 --- /dev/null +++ b/deployment/src/test/resources/testcontainer/rabbitmq/rabbitmq-properties.properties @@ -0,0 +1,3 @@ +quarkus.log.level=INFO + +#Nothing special here, see TestConfig class for properties with test-container usage. diff --git a/pom.xml b/pom.xml index 5e85326..f54719f 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,9 @@ true 1.11.0.Final 3.8.1 + 3.0.0 5.10.0 + 1.15.1 diff --git a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientBuildConfig.java b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientBuildConfig.java index a84fdfc..465605f 100644 --- a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientBuildConfig.java +++ b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientBuildConfig.java @@ -4,6 +4,11 @@ import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; +/** + * RabbitMQ client build time configuration. + * + * @author b.passon + */ @ConfigRoot(name = "rabbitmqclient", phase = ConfigPhase.BUILD_TIME) public class RabbitMQClientBuildConfig { diff --git a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientConfig.java b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientConfig.java index 425848f..602bb1f 100644 --- a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientConfig.java +++ b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientConfig.java @@ -8,6 +8,11 @@ import io.quarkus.runtime.annotations.*; +/** + * RabbitMQ client configuration. + * + * @author b.passon + */ @ConfigRoot(name = "rabbitmqclient", phase = ConfigPhase.RUN_TIME) public class RabbitMQClientConfig { @@ -18,7 +23,11 @@ public class RabbitMQClientConfig { public Optional uri; /** - * Broker addresses for creating connections + * Broker addresses for creating connections. + *

+ * When specified, {@code quarkus.rabbitmqclient.hostname} and + * {@code quarkus.rabbitmqclient.port} are ignored. + *

*/ @ConfigItem @ConfigDocMapKey("broker-name") diff --git a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientException.java b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientException.java index 64281ad..34e1c95 100644 --- a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientException.java +++ b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientException.java @@ -1,19 +1,12 @@ package io.quarkiverse.rabbitmqclient; +/** + * Generic exception used when things go wrong. + * + * @author b.passon + */ public class RabbitMQClientException extends RuntimeException { - /** - * Constructs a new RabbitMQ client exception with the specified detail message. - * The cause is not initialized, and may subsequently be initialized by a - * call to {@link #initCause}. - * - * @param message the detail message. The detail message is saved for - * later retrieval by the {@link #getMessage()} method. - */ - public RabbitMQClientException(String message) { - super(message); - } - /** * Constructs a new RabbitMQ client exception with the specified detail message and * cause. diff --git a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientProducer.java b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientProducer.java index 7fdcb77..0244d80 100644 --- a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientProducer.java +++ b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQClientProducer.java @@ -1,6 +1,7 @@ package io.quarkiverse.rabbitmqclient; import javax.annotation.PreDestroy; +import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.Produces; import javax.inject.Singleton; @@ -12,7 +13,7 @@ * * @author b.passon */ -@Singleton +@ApplicationScoped public class RabbitMQClientProducer { private RabbitMQClientImpl rabbitMQClient; @@ -28,9 +29,7 @@ public class RabbitMQClientProducer { @Singleton @Produces public RabbitMQClient rabbitMQClient(RabbitMQClientConfig config, TlsConfig tlsConfig) { - if (rabbitMQClient == null) { - rabbitMQClient = new RabbitMQClientImpl(config, tlsConfig); - } + rabbitMQClient = new RabbitMQClientImpl(config, tlsConfig); return rabbitMQClient; } @@ -39,6 +38,9 @@ public RabbitMQClient rabbitMQClient(RabbitMQClientConfig config, TlsConfig tlsC */ @PreDestroy public void destroy() { - rabbitMQClient.disconnect(); + // check for null, the producer method might never have been called. + if (rabbitMQClient != null) { + rabbitMQClient.disconnect(); + } } } diff --git a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQHelper.java b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQHelper.java index 3bf6061..de31a3a 100644 --- a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQHelper.java +++ b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQHelper.java @@ -1,9 +1,6 @@ package io.quarkiverse.rabbitmqclient; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Properties; +import java.util.*; import java.util.stream.Collectors; import com.rabbitmq.client.Address; @@ -63,6 +60,10 @@ private static ConnectionFactory newConnectionFactory(RabbitMQClientConfig confi /** * Resolves the available broker addresses based on the given configuration. + *

+ * If multiple named brokers are supplied, the {@link RabbitMQClientConfig} {@code hostname} + * and {@code port} settings are ignored. + *

* * @param config the {@link RabbitMQClientConfig}. * @return a list of RabbitMQ broker addresses. diff --git a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQReadyCheck.java b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQReadyCheck.java index a54da8e..6326787 100644 --- a/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQReadyCheck.java +++ b/runtime/src/main/java/io/quarkiverse/rabbitmqclient/RabbitMQReadyCheck.java @@ -1,12 +1,15 @@ package io.quarkiverse.rabbitmqclient; import java.net.Socket; +import java.util.HashMap; +import java.util.Map; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import org.eclipse.microprofile.health.HealthCheck; import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; import org.eclipse.microprofile.health.Readiness; import com.rabbitmq.client.Address; @@ -20,21 +23,33 @@ @ApplicationScoped public class RabbitMQReadyCheck implements HealthCheck { + public static final String HEALTH_CHECK_NAME = "quarkus-rabbitmq-client"; @Inject RabbitMQClientConfig config; @Override public HealthCheckResponse call() { - if (atLeastOneBrokerIsAlive()) { - return HealthCheckResponse.up("At least one RabbitMQ broker is available."); - } - return HealthCheckResponse.down("No RabbitMQ broker is available."); + return checkAllBrokers(); } - private boolean atLeastOneBrokerIsAlive() { - return RabbitMQHelper.resolveBrokerAddresses(config) - .stream() - .anyMatch(this::isBrokerAvailable); + private HealthCheckResponse checkAllBrokers() { + Map data = new HashMap<>(); + RabbitMQHelper.resolveBrokerAddresses(config) + .forEach((a) -> { + if (isBrokerAvailable(a)) { + data.put(a.toString(), HealthCheckResponse.State.UP); + } else { + data.put(a.toString(), HealthCheckResponse.State.DOWN); + } + }); + HealthCheckResponseBuilder builder = HealthCheckResponse.builder(); + builder.name(HEALTH_CHECK_NAME); + builder.state(data.values().stream().allMatch(s -> s == HealthCheckResponse.State.UP)); + data.forEach((a, s) -> { + builder.withData(a, s.name()); + }); + return builder.build(); + } private boolean isBrokerAvailable(Address address) { From 66210c1bfed8cf1ee910714ccae4692157e37844 Mon Sep 17 00:00:00 2001 From: Bas Passon Date: Mon, 1 Feb 2021 10:36:04 +0100 Subject: [PATCH 6/8] Removed commented code --- .../deployment/QuarkusRabbitmqClientProcessor.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/deployment/src/main/java/io/quarkiverse/rabbitmqclient/deployment/QuarkusRabbitmqClientProcessor.java b/deployment/src/main/java/io/quarkiverse/rabbitmqclient/deployment/QuarkusRabbitmqClientProcessor.java index e38e324..e3145f9 100644 --- a/deployment/src/main/java/io/quarkiverse/rabbitmqclient/deployment/QuarkusRabbitmqClientProcessor.java +++ b/deployment/src/main/java/io/quarkiverse/rabbitmqclient/deployment/QuarkusRabbitmqClientProcessor.java @@ -22,11 +22,6 @@ FeatureBuildItem feature() { return new FeatureBuildItem(FEATURE); } - // @BuildStep - // void addDependencies(BuildProducer indexDependency) { - // indexDependency.produce(new IndexDependencyBuildItem("com.rabbitmq", "amqp-client")); - // } - @BuildStep HealthBuildItem addHealthCheck(RabbitMQClientBuildConfig buildTimeConfig) { return new HealthBuildItem("io.quarkiverse.rabbitmqclient.RabbitMQReadyCheck", From c5a04614c66bdbf7410bb49cb8183728fc911e11 Mon Sep 17 00:00:00 2001 From: Bas Passon Date: Mon, 1 Feb 2021 11:25:53 +0100 Subject: [PATCH 7/8] Added extension descriptor --- .../src/main/resources/META-INF/quarkus-extension.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 runtime/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000..64b64c6 --- /dev/null +++ b/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,9 @@ +--- +name: "RabbitMQ Client" +metadata: + keywords: + - "rabbitmq-client" + - "rabbitmq" + categories: + - "messaging" + status: "experimental" \ No newline at end of file From 2ec6d4902c5f9c867bbb766acea1139358c63014 Mon Sep 17 00:00:00 2001 From: Bas Passon Date: Mon, 1 Feb 2021 13:34:15 +0100 Subject: [PATCH 8/8] Removed release trigger from curren PR to create a separate one. --- .github/project.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/project.yml b/.github/project.yml index 316d19a..90f4f23 100644 --- a/.github/project.yml +++ b/.github/project.yml @@ -1,5 +1,5 @@ -name: Quarkus RabbitMQ Client Extension +name: Quarkiverse Extension release: - current-version: 0.1.0 - next-version: 0.2.0-SNAPSHOT + current-version: 0 + next-version: 0.0.1-SNAPSHOT