This is an automated email from the ASF dual-hosted git repository. jamesnetherton pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-quarkus-examples.git
commit be38e501a344bb2e158c1d10f78ea53331928ea7 Author: aldettinger <aldettin...@gmail.com> AuthorDate: Thu Sep 7 20:35:36 2023 +0200 Add an example for JPA based idempotent repository --- docs/modules/ROOT/attachments/examples.json | 5 + jpa-idempotent-repository/README.adoc | 230 ++++++++++++ .../eclipse-formatter-config.xml | 276 ++++++++++++++ jpa-idempotent-repository/pom.xml | 399 +++++++++++++++++++++ jpa-idempotent-repository/schema.png | Bin 0 -> 97169 bytes jpa-idempotent-repository/schemas-source.odp | Bin 0 -> 19800 bytes .../idempotent/repository/CostlyApiService.java | 53 +++ .../repository/ExampleHarnessRoutes.java | 68 ++++ .../idempotent/repository/JpaIdempotentRoute.java | 75 ++++ .../src/main/kubernetes/kubernetes.yml | 94 +++++ .../src/main/kubernetes/openshift.yml | 94 +++++ .../src/main/resources/application.properties | 67 ++++ .../idempotent/repository/DerbyTestResource.java | 79 ++++ .../repository/JpaIdempotentRepositoryIT.java | 24 ++ .../repository/JpaIdempotentRepositoryTest.java | 45 +++ .../src/test/resources/init.sql | 20 ++ 16 files changed, 1529 insertions(+) diff --git a/docs/modules/ROOT/attachments/examples.json b/docs/modules/ROOT/attachments/examples.json index 0bda1c5..7e39740 100644 --- a/docs/modules/ROOT/attachments/examples.json +++ b/docs/modules/ROOT/attachments/examples.json @@ -39,6 +39,11 @@ "description": "Shows how to run a Camel Quarkus application that supports JTA transactions on three external transactional resources: a database (MySQL), a messaging broker (Artemis) and a simulated XAResource which can demonstrate the commit, rollback and crash recovery.", "link": "https://github.com/apache/camel-quarkus-examples/tree/main/jms-jpa" }, + { + "title": "JPA idempotent repository", + "description": "Shows how to consume a message only once, even when the message is delivered multiple times", + "link": "https://github.com/apache/camel-quarkus-examples/tree/main/jpa-idempotent-repository" + }, { "title": "JTA and JPA", "description": "Shows how to run a Camel Quarkus application that supports JTA transactions on two external transactional resources: a database (MySQL) and a simulate XAResource which can demonstrate the commit, rollback and crash recovery.", diff --git a/jpa-idempotent-repository/README.adoc b/jpa-idempotent-repository/README.adoc new file mode 100644 index 0000000..0034b80 --- /dev/null +++ b/jpa-idempotent-repository/README.adoc @@ -0,0 +1,230 @@ += JPA idempotent repository: A Camel Quarkus example +:cq-example-description: An example that shows how to consume a message only once, even when the message is delivered multiple times + +{cq-description} + +TIP: Check the https://camel.apache.org/camel-quarkus/latest/first-steps.html[Camel Quarkus User guide] for prerequisites +and other general information. + +Suppose an application needs to invoke a costly API. Each time a duplicate message would be processed, then a bunch of money would be lost. In such situations, it could make sense to prevent some duplicate calls by using the https://camel.apache.org/components/latest/eips/idempotentConsumer-eip.html[idempotent consumer] EIP. +Let's see an example with the schema below: + +image::schema.png[] + +As one could see in the schema, using the idempotent consumer pattern in Camel is as simple as creating a route using the `idempotentConsumer` and `idempotentRepository` keywords. In this example, the idempotent repository is a database that is edited and read through JPA. Under the hood, this database will keep track of messages that have already been processed. + +Note that JPA is not the only option when it comes to selecting an `idempotentRepository` implementation. +Other choices are available as listed in the https://camel.apache.org/components/latest/eips/idempotentConsumer-eip.html#_idempotent_consumer_implementations[documentation]. + +In this example, let's focus on JPA and see more details about how to execute such a route in the next sections below. + +== Start in Development mode + +Let's start by executing the command below: + +[source,shell] +---- +mvn clean compile quarkus:dev +---- + +The above command compiles the project, starts the application and lets the Quarkus tooling watch for changes in your workspace. +Any modifications in your project will automatically take effect in the running application. + +TIP: Please refer to the Development mode section of +https://camel.apache.org/camel-quarkus/latest/first-steps.html#_development_mode[Camel Quarkus User guide] for more details. + +It should be possible now to see some log messages appearing on the console. +Note how some files with different content are generated `3,5,7...` +Each time such a file is consumed by the route, a costly API is called. +However, a file with content `1` is regularly generated. +This duplicate file is problematic as it will generate undue calls to the costly API too frequently. +This is where the https://camel.apache.org/components/latest/eips/idempotentConsumer-eip.html[idempotent consumer] enter the game. +The source code could be found in the source file named `src/main/java/org/acme/jpa/idempotent/repository/JpaIdempotentRoute.java`. +The camel application should produce logs as below: + +[source,shell] +---- +2023-09-15 15:47:49,477 INFO [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) ----------------------------------------------------------------- +2023-09-15 15:47:49,478 INFO [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) Creating an example input file with content 1 +2023-09-15 15:47:50,974 INFO [route1] (Camel (camel-1) thread #1 - file://target/input-files) Received an example input file having the content 1 +2023-09-15 15:47:51,167 INFO [route1] (Camel (camel-1) thread #1 - file://target/input-files) The file was not a duplicate, invoke the costly API +2023-09-15 15:47:51,230 INFO [org.acm.jpa.ide.rep.CostlyApiService] (vert.x-worker-thread-1) Costly API has been called with new content => GOOD +2023-09-15 15:47:59,475 INFO [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) ----------------------------------------------------------------- +2023-09-15 15:47:59,477 INFO [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) Creating an example input file with content 3 +2023-09-15 15:48:00,758 INFO [route1] (Camel (camel-1) thread #1 - file://target/input-files) Received an example input file having the content 3 +2023-09-15 15:48:00,761 INFO [route1] (Camel (camel-1) thread #1 - file://target/input-files) The file was not a duplicate, invoke the costly API +2023-09-15 15:48:00,765 INFO [org.acm.jpa.ide.rep.CostlyApiService] (vert.x-worker-thread-1) Costly API has been called with new content => GOOD +2023-09-15 15:48:09,475 INFO [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) ----------------------------------------------------------------- +2023-09-15 15:48:09,477 INFO [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) Creating an example input file with content 1 +2023-09-15 15:48:10,777 INFO [route1] (Camel (camel-1) thread #1 - file://target/input-files) Received an example input file having the content 1 +2023-09-15 15:48:19,475 INFO [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) ----------------------------------------------------------------- +2023-09-15 15:48:19,477 INFO [route2] (Camel (camel-1) thread #2 - timer://createExampleInputFiles) Creating an example input file with content 5 +2023-09-15 15:48:20,796 INFO [route1] (Camel (camel-1) thread #1 - file://target/input-files) Received an example input file having the content 5 +2023-09-15 15:48:20,801 INFO [route1] (Camel (camel-1) thread #1 - file://target/input-files) The file was not a duplicate, invoke the costly API +2023-09-15 15:48:20,804 INFO [org.acm.jpa.ide.rep.CostlyApiService] (vert.x-worker-thread-1) Costly API has been called with new content => GOOD +---- + +When running in dev mode, the idempotent consumer is storing the list of already processed messages in-memory, into a h2 database. +Later on, another database will be used when we'll package and run the application. +Indeed, the duplicate messages will then be stored in files, into a derby database. + +== Starting and initializing the derby database in a container + +Before packaging and running the application in JVM mode, we need to start and initialize a derby database in a container. +So, in a first shell, please launch a derby database container: + +[source,shell] +---- +docker run -p 1527:1527 az82/docker-derby:10.16 +---- + +And from a second shell, please run the commands below in order to initialize the derby database: + +[source,shell] +---- +DERBY_DOCKER_ID=$(docker ps -q --filter ancestor=az82/docker-derby) +docker cp src/test/resources/init.sql ${DERBY_DOCKER_ID}:/init.sql +docker exec -it ${DERBY_DOCKER_ID} java -Djdbc.drivers=org.apache.derbbc.EmbeddedDriver org.apache.derby.tools.ij /init.sql +---- + +It should output some logs like below: + +[source,shell] +---- +$ DERBY_DOCKER_ID=$(docker ps -q --filter ancestor=az82/docker-derby) + +$ docker cp src/test/resources/init.sql ${DERBY_DOCKER_ID}:/init.sql +Successfully copied 2.05kB to c88edda502f7:/init.sql + +$ docker exec -it ${DERBY_DOCKER_ID} java -Djdbc.drivers=org.apache.derbbc.EmbeddedDriver org.apache.derby.tools.ij /init.sql +ij version 10.16 +ij> CONNECT 'jdbc:derby:my-db;create=true'; +ij> CREATE TABLE CAMEL_MESSAGEPROCESSED ( processorName VARCHAR(255), messageId VARCHAR(100), createdAt TIMESTAMP, PRIMARY KEY (processorName, messageId) ); +0 rows inserted/updated/deleted +ij> CREATE SEQUENCE CAMEL_MESSAGEPROCESSED_SEQ AS INT MAXVALUE 999999 CYCLE; +0 rows inserted/updated/deleted +---- + +=== Package and run the application + +Once you are done with developing you may want to package and run the application. + +TIP: Find more details about the JVM mode and Native mode in the Package and run section of +https://camel.apache.org/camel-quarkus/latest/first-steps.html#_package_and_run_the_application[Camel Quarkus User guide] + +==== JVM mode + +[source,shell] +---- +mvn clean package -DskipTests +java -jar target/quarkus-app/quarkus-run.jar +---- + +Please, note that the shell running the derby database should react by printing some logs as below: + +[source,shell] +---- +Booting Derby version The Apache Software Foundation - Apache Derby - 10.16.1.1 - (1901046): instance a816c00e-018a-996e-54bf-00003e718008 +on database directory /dbs/my-db with class loader jdk.internal.loader.ClassLoaders$AppClassLoader@5c626da3 +Loaded from file:/derby/lib/derby.jar +java.vendor=Eclipse Adoptium +java.runtime.version=17.0.4.1+1 +user.dir=/dbs +os.name=Linux +os.arch=amd64 +os.version=4.18.0-477.21.1.el8_8.x86_64 +derby.system.home=null +derby.stream.error.field=java.lang.System.out +Database Class Loader started - derby.database.classpath='' +---- + +Beyond that, notice how the application behaves the same way. +The only variation compared to the dev mode is actually that the idempotent repository is now a derby database running in a container. + +==== Native mode + +IMPORTANT: Native mode requires having GraalVM and other tools installed. Please check the Prerequisites section +of https://camel.apache.org/camel-quarkus/latest/first-steps.html#_prerequisites[Camel Quarkus User guide]. + +To prepare a native executable using GraalVM, run the following commands: + +[source,shell] +---- +mvn clean package -DskipTests -Pnative +./target/*-runner +---- + +The compilation is a bit slower. Beyond that, notice how the application behaves the same way. +The only variation compared to the JVM mode is actually that the application was packaged as a native executable. + +==== Deploying to Kubernetes + +You can build a container image for the application like this. Refer to the https://quarkus.io/guides/deploying-to-kubernetes[Quarkus Kubernetes guide] for options around customizing image names, registries etc. + +[source,shell] +---- +mvn clean package -DskipTests -Dquarkus.container-image.build=true +---- + +If you are using a local development cluster like Kind or k3s, you can use host the container image on your local host. Or, with minikube, use the Docker daemon from the cluster virtual machine `eval $(minikube docker-env)`. Otherwise, you'll need to push the image to a registry of your choosing. + +Next apply the necessary resources to the cluster if needed: + +[source,shell] +---- +kubectl apply -f target/kubernetes/kubernetes.yml +---- + +TIP: You can build & deploy in one single step by doing `mvn clean package -DskipTests -Dquarkus.kubernetes.deploy=true` + +Check pods are running by executing: + +[source,shell] +---- +kubectl get pods +---- + +We expect a list of two pods similar to below: + +[source,shell] +---- +NAME READY STATUS RESTARTS AGE +camel-quarkus-examples-derby-database-deployment-76f6dc9bdnwwxn 1/1 Running 0 23s +camel-quarkus-examples-jpa-idempotent-repository-7c74b9cf5ph68r 1/1 Running 1 (18s ago) 23s +---- + +Now, let's tail the application logs: + +[source,shell] +---- +kubectl logs -f camel-quarkus-examples-jpa-idempotent-repository-56999fcffb6qv2 +---- + +To clean up do: + +[source,shell] +---- +kubectl delete all -l app.kubernetes.io/name=camel-quarkus-examples-jpa-idempotent-repository +kubectl delete all -l app.kubernetes.io/name=camel-quarkus-examples-derby-database +kubectl delete configmap -l app.kubernetes.io/name=camel-quarkus-examples-derby-database +---- + +[NOTE] +==== +If you need to configure container resource limits & requests, or enable the Quarkus Kubernetes client to trust self signed certificates, you can find these configuration options in `src/main/resources/application.properties`. Simply uncomment them and set your desired values. +==== + +==== Deploying to OpenShift + +In order to start a Source To Image (S2I) build and deploy the application, let's execute the command below: + +[source,shell] +---- +mvn clean package -DskipTests -Dquarkus.kubernetes.deploy=true -Dopenshift +---- + +You can check the pod status and tail logs using the commands mentioned above in the Kubernetes section. Use the `oc` binary instead of `kubectl` if preferred. + +== Feedback + +Please report bugs and propose improvements via https://github.com/apache/camel-quarkus/issues[GitHub issues of Camel Quarkus] project. diff --git a/jpa-idempotent-repository/eclipse-formatter-config.xml b/jpa-idempotent-repository/eclipse-formatter-config.xml new file mode 100644 index 0000000..2248b2b --- /dev/null +++ b/jpa-idempotent-repository/eclipse-formatter-config.xml @@ -0,0 +1,276 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> +<profiles version="8"> + <profile name="Camel Java Conventions" version="8" kind="CodeFormatterProfile"> + <setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/> + <setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/> + <setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.comment.format_comments" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.comment.indent_return_description" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="120"/> + <setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/> + <setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/> + <setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.indentation.size" value="8"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/> + <setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.lineSplit" value="128"/> + <setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/> + <setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/> + <setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/> + <setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/> + <setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/> + <setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/> + <setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="CHECKSTYLE:OFF"/> + <setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="CHECKSTYLE:ON"/> + </profile> +</profiles> diff --git a/jpa-idempotent-repository/pom.xml b/jpa-idempotent-repository/pom.xml new file mode 100644 index 0000000..f7ffe8b --- /dev/null +++ b/jpa-idempotent-repository/pom.xml @@ -0,0 +1,399 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <artifactId>camel-quarkus-examples-jpa-idempotent-repository</artifactId> + <groupId>org.apache.camel.quarkus.examples</groupId> + <version>3.5.0-SNAPSHOT</version> + + <name>Camel Quarkus :: Examples :: JPA Idempotent Repository</name> + <description>Camel Quarkus Example :: JPA Idempotent Repository</description> + + <properties> + <quarkus.platform.version>3.4.1</quarkus.platform.version> + <camel-quarkus.platform.version>3.5.0-SNAPSHOT</camel-quarkus.platform.version> + + <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id> + <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id> + <camel-quarkus.platform.group-id>org.apache.camel.quarkus</camel-quarkus.platform.group-id> + <camel-quarkus.platform.artifact-id>camel-quarkus-bom</camel-quarkus.platform.artifact-id> + + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <maven.compiler.target>17</maven.compiler.target> + <maven.compiler.source>17</maven.compiler.source> + <maven.compiler.testTarget>${maven.compiler.target}</maven.compiler.testTarget> + <maven.compiler.testSource>${maven.compiler.source}</maven.compiler.testSource> + + <formatter-maven-plugin.version>2.23.0</formatter-maven-plugin.version> + <groovy-maven-plugin.version>2.1.1</groovy-maven-plugin.version> + <impsort-maven-plugin.version>1.9.0</impsort-maven-plugin.version> + <license-maven-plugin.version>4.2</license-maven-plugin.version> + <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version> + <maven-jar-plugin.version>3.3.0</maven-jar-plugin.version> + <maven-resources-plugin.version>3.3.1</maven-resources-plugin.version> + <maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version> + </properties> + + <dependencyManagement> + <dependencies> + <!-- Import BOM --> + <dependency> + <groupId>${quarkus.platform.group-id}</groupId> + <artifactId>${quarkus.platform.artifact-id}</artifactId> + <version>${quarkus.platform.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + <dependency> + <groupId>${camel-quarkus.platform.group-id}</groupId> + <artifactId>${camel-quarkus.platform.artifact-id}</artifactId> + <version>${camel-quarkus.platform.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + <dependency> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-bean</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-file</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-http</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-jpa</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-platform-http</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-timer</artifactId> + </dependency> + <!-- Note we added a dependency to quarkus-h2 to have an in memory + database during dev mode --> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-jdbc-h2</artifactId> + </dependency> + <!-- While in test and prod we use a derby database --> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-jdbc-derby</artifactId> + </dependency> + + <!-- Test --> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-junit5</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.awaitility</groupId> + <artifactId>awaitility</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.rest-assured</groupId> + <artifactId>rest-assured</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>testcontainers</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.codehaus.gmaven</groupId> + <artifactId>groovy-maven-plugin</artifactId> + <version>${groovy-maven-plugin.version}</version> + </plugin> + + <plugin> + <groupId>net.revelc.code.formatter</groupId> + <artifactId>formatter-maven-plugin</artifactId> + <version>${formatter-maven-plugin.version}</version> + <configuration> + <configFile>${maven.multiModuleProjectDirectory}/eclipse-formatter-config.xml</configFile> + <lineEnding>LF</lineEnding> + </configuration> + </plugin> + + <plugin> + <groupId>net.revelc.code</groupId> + <artifactId>impsort-maven-plugin</artifactId> + <version>${impsort-maven-plugin.version}</version> + <configuration> + <groups>java.,javax.,org.w3c.,org.xml.,junit.</groups> + <removeUnused>true</removeUnused> + <staticAfter>true</staticAfter> + <staticGroups>java.,javax.,org.w3c.,org.xml.,junit.</staticGroups> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>${maven-compiler-plugin.version}</version> + <configuration> + <showDeprecation>true</showDeprecation> + <showWarnings>true</showWarnings> + <compilerArgs> + <arg>-Xlint:unchecked</arg> + </compilerArgs> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>${maven-surefire-plugin.version}</version> + <configuration> + <failIfNoTests>false</failIfNoTests> + <systemProperties> + <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager> + </systemProperties> + </configuration> + </plugin> + + <plugin> + <groupId>${quarkus.platform.group-id}</groupId> + <artifactId>quarkus-maven-plugin</artifactId> + <version>${quarkus.platform.version}</version> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <version>${maven-surefire-plugin.version}</version> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>${maven-jar-plugin.version}</version> + </plugin> + + <plugin> + <groupId>com.mycila</groupId> + <artifactId>license-maven-plugin</artifactId> + <version>${license-maven-plugin.version}</version> + <configuration> + <failIfUnknown>true</failIfUnknown> + <header>${maven.multiModuleProjectDirectory}/header.txt</header> + <excludes> + <exclude>**/*.adoc</exclude> + <exclude>**/*.odp</exclude> + <exclude>**/*.txt</exclude> + <exclude>**/LICENSE.txt</exclude> + <exclude>**/LICENSE</exclude> + <exclude>**/NOTICE.txt</exclude> + <exclude>**/NOTICE</exclude> + <exclude>**/README</exclude> + <exclude>**/pom.xml.versionsBackup</exclude> + </excludes> + <mapping> + <java>SLASHSTAR_STYLE</java> + <properties>CAMEL_PROPERTIES_STYLE</properties> + <kt>SLASHSTAR_STYLE</kt> + </mapping> + <headerDefinitions> + <headerDefinition>${maven.multiModuleProjectDirectory}/license-properties-headerdefinition.xml</headerDefinition> + </headerDefinitions> + </configuration> + </plugin> + </plugins> + </pluginManagement> + + <plugins> + <plugin> + <groupId>${quarkus.platform.group-id}</groupId> + <artifactId>quarkus-maven-plugin</artifactId> + <executions> + <execution> + <id>build</id> + <goals> + <goal>build</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>net.revelc.code.formatter</groupId> + <artifactId>formatter-maven-plugin</artifactId> + <executions> + <execution> + <id>format</id> + <goals> + <goal>format</goal> + </goals> + <phase>process-sources</phase> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>net.revelc.code</groupId> + <artifactId>impsort-maven-plugin</artifactId> + <executions> + <execution> + <id>sort-imports</id> + <goals> + <goal>sort</goal> + </goals> + <phase>process-sources</phase> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.codehaus.gmaven</groupId> + <artifactId>groovy-maven-plugin</artifactId> + <executions> + <execution> + <id>sync-k8s-resource-versions</id> + <inherited>false</inherited> + <goals> + <goal>execute</goal> + </goals> + <phase>process-sources</phase> + <configuration> + <source> + import java.util.regex.Matcher + import java.util.regex.Pattern + + Pattern pattern = Pattern.compile("app.kubernetes.io/version: .*") + + ["kubernetes", "openshift"].each { k8sResource -> + String sanitizedBasedir = project.basedir.path.replace('\\', '/') + File file = new File("${sanitizedBasedir}/src/main/kubernetes/${k8sResource}.yml") + String content = file.text + Matcher matcher = pattern.matcher(content) + + if (matcher.find()) { + String updatedContent = matcher.replaceAll("app.kubernetes.io/version: ${project.version}") + if (updatedContent != content) { + log.info("Updating app.kubernetes.io/version label to ${project.version} in ${file.path}") + file.write(updatedContent) + } + } + } + </source> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>native</id> + <activation> + <property> + <name>native</name> + </property> + </activation> + <properties> + <quarkus.package.type>native</quarkus.package.type> + </properties> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>integration-test</goal> + <goal>verify</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + <profile> + <id>kubernetes</id> + <activation> + <activeByDefault>true</activeByDefault> + <property> + <name>kubernetes</name> + </property> + </activation> + <dependencies> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-kubernetes</artifactId> + </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-container-image-jib</artifactId> + </dependency> + </dependencies> + </profile> + <profile> + <id>openshift</id> + <activation> + <property> + <name>openshift</name> + </property> + </activation> + <dependencies> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-openshift</artifactId> + </dependency> + </dependencies> + </profile> + <profile> + <id>skip-testcontainers-tests</id> + <activation> + <property> + <name>skip-testcontainers-tests</name> + </property> + </activation> + <properties> + <skipTests>true</skipTests> + </properties> + </profile> + </profiles> + +</project> diff --git a/jpa-idempotent-repository/schema.png b/jpa-idempotent-repository/schema.png new file mode 100644 index 0000000..0252310 Binary files /dev/null and b/jpa-idempotent-repository/schema.png differ diff --git a/jpa-idempotent-repository/schemas-source.odp b/jpa-idempotent-repository/schemas-source.odp new file mode 100644 index 0000000..0c40e43 Binary files /dev/null and b/jpa-idempotent-repository/schemas-source.odp differ diff --git a/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/CostlyApiService.java b/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/CostlyApiService.java new file mode 100644 index 0000000..09351f9 --- /dev/null +++ b/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/CostlyApiService.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.acme.jpa.idempotent.repository; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import io.quarkus.runtime.annotations.RegisterForReflection; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Named; +import org.apache.camel.Handler; +import org.jboss.logging.Logger; + +@ApplicationScoped +@RegisterForReflection +@Named("costlyApiService") +public class CostlyApiService { + + private static final Logger LOG = Logger.getLogger(CostlyApiService.class); + + private static Set<String> ALREADY_USED_CONTENT = ConcurrentHashMap.newKeySet(); + + /** + * The content parameter is populated with the incoming HTTP body sent to this API. + */ + @Handler + void invoke(String content) { + if (ALREADY_USED_CONTENT.contains(content)) { + LOG.info("Costly API has been called two times with the same content => TOO MUCH EXPENSIVE !"); + } else { + ALREADY_USED_CONTENT.add(content); + LOG.info("Costly API has been called with new content => GOOD"); + } + } + + String getContentSet() { + return String.join(",", ALREADY_USED_CONTENT); + } +} diff --git a/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/ExampleHarnessRoutes.java b/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/ExampleHarnessRoutes.java new file mode 100644 index 0000000..1e5fcb4 --- /dev/null +++ b/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/ExampleHarnessRoutes.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.acme.jpa.idempotent.repository; + +import jakarta.enterprise.context.ApplicationScoped; +import org.apache.camel.builder.RouteBuilder; + +/** + * The example harness provide a timer based route creating some input files for the main example route in + * JpaIdempotentRoute.java. It also defines a platform-http based route that mock the costly API. + */ +@ApplicationScoped +public class ExampleHarnessRoutes extends RouteBuilder { + + private static int COUNT = 1; + + @Override + public void configure() { + /** + * Generate some example input files + */ + from("timer:createExampleInputFiles?delay={{timer.delay}}&period={{timer.period}}&repeatCount={{timer.repeatCount}}") + /** + * Populate the content of each file with a series of odd numbers 1,3,1,5,1,7 Note that the value 1 is a + * doublon in the series, so some of the files have a duplicate content + */ + .setBody(e -> Integer.toString(++COUNT % 2 == 0 ? 1 : COUNT)) + .log("-----------------------------------------------------------------") + .log("Creating an example input file with content ${body}") + /** + * Arbitrary delay to ensure logs are printed in human friendly order most of the time + */ + .delay(constant(1000)) + /** + * Create the file with the generated content in the target/input-files folder + */ + .to("file:target/input-files"); + + /** + * Consume the incoming API calls. + */ + from("platform-http:/costly-api-call") + /** + * Delegate treatment to the bean named costlyApiService defined in CostlyApiService.java + */ + .bean("costlyApiService"); + + /** + * Creates a service that return the set of content that were already consumed. + */ + from("platform-http:/content-set") + .bean("costlyApiService", "getContentSet"); + } +} diff --git a/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/JpaIdempotentRoute.java b/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/JpaIdempotentRoute.java new file mode 100644 index 0000000..6ca97c9 --- /dev/null +++ b/jpa-idempotent-repository/src/main/java/org/acme/jpa/idempotent/repository/JpaIdempotentRoute.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.acme.jpa.idempotent.repository; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Named; +import jakarta.persistence.EntityManagerFactory; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.jpa.TransactionStrategy; +import org.apache.camel.processor.idempotent.jpa.JpaMessageIdRepository; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +@ApplicationScoped +public class JpaIdempotentRoute extends RouteBuilder { + + /** + * Gets the HTTP port where the example harness is listening for API calls. + */ + @ConfigProperty(name = "quarkus.http.port", defaultValue = "8085") + private int quarkusPlatformHttpPort; + + /** + * The camel route expect a bean named jpaIdempotentRepository of type IdempotentRepository to be present in the + * registry. One way to create such a bean is to use a producer method as shown below. + */ + @Produces + @Named + JpaMessageIdRepository jpaIdempotentRepository(EntityManagerFactory entityManagerFactory, + TransactionStrategy transactionStrategy) { + /** + * The JPA message id repository will store duplicate message IDs in a database. Distinct repository could be + * created on the same database by using different processor names. + */ + return new JpaMessageIdRepository(entityManagerFactory, transactionStrategy, "myProcessorName"); + } + + @Override + public void configure() { + /** + * Read the files generated by the example harness in the target/input-files folder + */ + from("file:target/input-files") + .log("Received an example input file having the content ${body}") + /** + * The idempotent consumer pattern could be used as below. All messages presented with the same body more + * than once will be filtered out. + */ + .idempotentConsumer(simple("${body}")) + /** + * A place is needed in order to keep track of duplicate message bodies, it's called an idempotent + * repository The idempotent repository could be provided as a bean from the registry like below. + */ + .idempotentRepository("jpaIdempotentRepository") + .log("The file was not a duplicate, invoke the costly API") + /** + * Sends the content of the file to the costly API simulated by the example harness. + */ + .toF("http://localhost:%s/costly-api-call", quarkusPlatformHttpPort); + } +} diff --git a/jpa-idempotent-repository/src/main/kubernetes/kubernetes.yml b/jpa-idempotent-repository/src/main/kubernetes/kubernetes.yml new file mode 100644 index 0000000..dfd3968 --- /dev/null +++ b/jpa-idempotent-repository/src/main/kubernetes/kubernetes.yml @@ -0,0 +1,94 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: camel-quarkus-examples-derby-database-deployment + labels: + app.kubernetes.io/name: camel-quarkus-examples-derby-database + app.kubernetes.io/version: 3.5.0-SNAPSHOT +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: camel-quarkus-examples-derby-database + app.kubernetes.io/version: 3.5.0-SNAPSHOT + template: + metadata: + labels: + app.kubernetes.io/name: camel-quarkus-examples-derby-database + app.kubernetes.io/version: 3.5.0-SNAPSHOT + spec: + containers: + - name: derby-database + # Use a default configured derby database for example purpose, think twice before deploying to production + image: az82/docker-derby:10.16 + ports: + - containerPort: 1527 + volumeMounts: + # The /derby-init folder contains the SQL init script to create the database, the table and the sequence + - name: derby-database-init-script-volume + mountPath: /derby-init + # The /dbs folder is where the actual database content is stored + - name: derby-database-data-volume + mountPath: /dbs + lifecycle: + postStart: + # Execute the SQL init script after the derby container has started + exec: + command: ["java", "-Djdbc.drivers=org.apache.derbbc.EmbeddedDriver", "org.apache.derby.tools.ij", "/derby-init/init.sql"] + volumes: + # Create a volume in order to store the SQL init file + - name: derby-database-init-script-volume + configMap: + name: derby-database-init-script-config-map + defaultMode: 0744 + # Explicitly create an empty dir volume in order to ensure read/write access needed to store database files + - name: derby-database-data-volume + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: camel-quarkus-examples-derby-database + app.kubernetes.io/version: 3.5.0-SNAPSHOT + name: derby-database +spec: + ports: + - name: derby + port: 1527 + targetPort: 1527 + selector: + app.kubernetes.io/name: camel-quarkus-examples-derby-database + app.kubernetes.io/version: 3.5.0-SNAPSHOT + type: ClusterIP +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: derby-database-init-script-config-map + labels: + app.kubernetes.io/name: camel-quarkus-examples-derby-database + app.kubernetes.io/version: 3.5.0-SNAPSHOT +data: + init.sql: | + CONNECT 'jdbc:derby:my-db;create=true'; + CREATE TABLE CAMEL_MESSAGEPROCESSED ( processorName VARCHAR(255), messageId VARCHAR(100), createdAt TIMESTAMP, PRIMARY KEY (processorName, messageId) ); + CREATE SEQUENCE CAMEL_MESSAGEPROCESSED_SEQ AS INT MAXVALUE 999999 CYCLE; +--- \ No newline at end of file diff --git a/jpa-idempotent-repository/src/main/kubernetes/openshift.yml b/jpa-idempotent-repository/src/main/kubernetes/openshift.yml new file mode 100644 index 0000000..dfd3968 --- /dev/null +++ b/jpa-idempotent-repository/src/main/kubernetes/openshift.yml @@ -0,0 +1,94 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: camel-quarkus-examples-derby-database-deployment + labels: + app.kubernetes.io/name: camel-quarkus-examples-derby-database + app.kubernetes.io/version: 3.5.0-SNAPSHOT +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: camel-quarkus-examples-derby-database + app.kubernetes.io/version: 3.5.0-SNAPSHOT + template: + metadata: + labels: + app.kubernetes.io/name: camel-quarkus-examples-derby-database + app.kubernetes.io/version: 3.5.0-SNAPSHOT + spec: + containers: + - name: derby-database + # Use a default configured derby database for example purpose, think twice before deploying to production + image: az82/docker-derby:10.16 + ports: + - containerPort: 1527 + volumeMounts: + # The /derby-init folder contains the SQL init script to create the database, the table and the sequence + - name: derby-database-init-script-volume + mountPath: /derby-init + # The /dbs folder is where the actual database content is stored + - name: derby-database-data-volume + mountPath: /dbs + lifecycle: + postStart: + # Execute the SQL init script after the derby container has started + exec: + command: ["java", "-Djdbc.drivers=org.apache.derbbc.EmbeddedDriver", "org.apache.derby.tools.ij", "/derby-init/init.sql"] + volumes: + # Create a volume in order to store the SQL init file + - name: derby-database-init-script-volume + configMap: + name: derby-database-init-script-config-map + defaultMode: 0744 + # Explicitly create an empty dir volume in order to ensure read/write access needed to store database files + - name: derby-database-data-volume + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: camel-quarkus-examples-derby-database + app.kubernetes.io/version: 3.5.0-SNAPSHOT + name: derby-database +spec: + ports: + - name: derby + port: 1527 + targetPort: 1527 + selector: + app.kubernetes.io/name: camel-quarkus-examples-derby-database + app.kubernetes.io/version: 3.5.0-SNAPSHOT + type: ClusterIP +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: derby-database-init-script-config-map + labels: + app.kubernetes.io/name: camel-quarkus-examples-derby-database + app.kubernetes.io/version: 3.5.0-SNAPSHOT +data: + init.sql: | + CONNECT 'jdbc:derby:my-db;create=true'; + CREATE TABLE CAMEL_MESSAGEPROCESSED ( processorName VARCHAR(255), messageId VARCHAR(100), createdAt TIMESTAMP, PRIMARY KEY (processorName, messageId) ); + CREATE SEQUENCE CAMEL_MESSAGEPROCESSED_SEQ AS INT MAXVALUE 999999 CYCLE; +--- \ No newline at end of file diff --git a/jpa-idempotent-repository/src/main/resources/application.properties b/jpa-idempotent-repository/src/main/resources/application.properties new file mode 100644 index 0000000..c96b8e1 --- /dev/null +++ b/jpa-idempotent-repository/src/main/resources/application.properties @@ -0,0 +1,67 @@ +## --------------------------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## --------------------------------------------------------------------------- + +quarkus.banner.enabled = false + +# How often should input files be created +timer.period = 10000 +timer.delay = 1000 +timer.repeatCount = 0 + +# Few tricks needed to fix port for test purpose +quarkus.http.port = 8085 +quarkus.http.test-port = 8085 + +derby-hostname=localhost + +# Uncomment if your application image is to be pushed to an external registry +#quarkus.container-image.registry=my.docker-registry.net + +# Kubernetes + +# Uncomment to trust self signed certificates if they are presented by the Kubernetes API server +#quarkus.kubernetes-client.trust-certs=true + +quarkus.kubernetes.image-pull-policy=IfNotPresent +quarkus.kubernetes.env.vars.derby-hostname=derby-database + +# Uncomment to set resource limits +#quarkus.kubernetes.resources.requests.memory=64Mi +#quarkus.kubernetes.resources.requests.cpu=250m +#quarkus.kubernetes.resources.limits.memory=512Mi +#quarkus.kubernetes.resources.limits.cpu=1000m + +# OpenShift +quarkus.openshift.image-pull-policy=IfNotPresent +quarkus.openshift.env.vars.derby-hostname=derby-database + +# Uncomment to set resource limits +#quarkus.openshift.resources.requests.memory=64Mi +#quarkus.openshift.resources.requests.cpu=250m +#quarkus.openshift.resources.limits.memory=512Mi +#quarkus.openshift.resources.limits.cpu=1000m + +# JPA +quarkus.datasource.db-kind=derby +%dev.quarkus.datasource.db-kind=h2 + +quarkus.datasource.jdbc.url=jdbc:derby://${derby-hostname}:1527/my-db +%dev.quarkus.datasource.jdbc.url=jdbc:h2:mem:my-db;DB_CLOSE_DELAY=-1 + +quarkus.datasource.jdbc.max-size=8 + +quarkus.hibernate-orm.database.generation=drop-and-create diff --git a/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/DerbyTestResource.java b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/DerbyTestResource.java new file mode 100644 index 0000000..6e456a2 --- /dev/null +++ b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/DerbyTestResource.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.acme.jpa.idempotent.repository; + +import java.util.Map; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import org.apache.camel.util.CollectionHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.MountableFile; +import org.testcontainers.utility.TestcontainersConfiguration; + +/** + * The derby test resource starts a derby container. It uses fixed port number 1527. + */ +public class DerbyTestResource<T extends GenericContainer> implements QuarkusTestResourceLifecycleManager { + private static final Logger LOGGER = LoggerFactory.getLogger(DerbyTestResource.class); + private static final String DERBY_IMAGE_NAME = "az82/docker-derby:10.16"; + private static final int DERBY_PORT = 1527; + + private GenericContainer container; + + @Override + public Map<String, String> start() { + + LOGGER.info(TestcontainersConfiguration.getInstance().toString()); + + try { + container = new GenericContainer(DERBY_IMAGE_NAME) + .withExposedPorts(DERBY_PORT) + .withCopyFileToContainer(MountableFile.forClasspathResource("init.sql"), "/init.sql") + .waitingFor(Wait.forListeningPort()); + container.start(); + + container.execInContainer("java", "-Djdbc.drivers=org.apache.derbbc.EmbeddedDriver", + "org.apache.derby.tools.ij", "/init.sql"); + + return CollectionHelper.mapOf("quarkus.datasource.jdbc.url", + "jdbc:derby://localhost:" + container.getMappedPort(DERBY_PORT) + "/my-db", "timer.period", "100", + "timer.delay", + "0", "timer.repeatCount", "4"); + } catch (Exception e) { + LOGGER.error("An error occurred while starting the derby container", e); + throw new RuntimeException(e); + } + } + + protected void startContainer() { + container.start(); + } + + @Override + public void stop() { + try { + if (container != null) { + container.stop(); + } + } catch (Exception e) { + // ignored + } + } +} diff --git a/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryIT.java b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryIT.java new file mode 100644 index 0000000..f193ad7 --- /dev/null +++ b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryIT.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.acme.jpa.idempotent.repository; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class JpaIdempotentRepositoryIT extends JpaIdempotentRepositoryTest { + +} diff --git a/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTest.java b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTest.java new file mode 100644 index 0000000..e49b5da --- /dev/null +++ b/jpa-idempotent-repository/src/test/java/org/acme/jpa/idempotent/repository/JpaIdempotentRepositoryTest.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.acme.jpa.idempotent.repository; + +import java.util.concurrent.TimeUnit; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import org.junit.jupiter.api.Test; + +import static org.awaitility.Awaitility.await; + +@QuarkusTest +@QuarkusTestResource(DerbyTestResource.class) +public class JpaIdempotentRepositoryTest { + + @Test + public void contentSetShouldStartWithOneThreeFive() { + + await().atMost(30L, TimeUnit.SECONDS).pollDelay(500, TimeUnit.MILLISECONDS).until(() -> { + String contentSet = RestAssured + .when() + .get("/content-set") + .then() + .extract().asString(); + + return contentSet != null && contentSet.startsWith("1,3,5"); + }); + } +} diff --git a/jpa-idempotent-repository/src/test/resources/init.sql b/jpa-idempotent-repository/src/test/resources/init.sql new file mode 100644 index 0000000..bac7b01 --- /dev/null +++ b/jpa-idempotent-repository/src/test/resources/init.sql @@ -0,0 +1,20 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +CONNECT 'jdbc:derby:my-db;create=true'; +CREATE TABLE CAMEL_MESSAGEPROCESSED ( processorName VARCHAR(255), messageId VARCHAR(100), createdAt TIMESTAMP, PRIMARY KEY (processorName, messageId) ); +CREATE SEQUENCE CAMEL_MESSAGEPROCESSED_SEQ AS INT MAXVALUE 999999 CYCLE;