You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ja...@apache.org on 2023/10/25 12:44:43 UTC
[camel-quarkus-examples] 03/14: Add an example for JPA based idempotent repository
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 <al...@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;