You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2023/05/02 12:53:30 UTC
[camel] branch main updated: CAMEL-19304: camel-jpa implement paging (#9970)
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new bbd1140ebd5 CAMEL-19304: camel-jpa implement paging (#9970)
bbd1140ebd5 is described below
commit bbd1140ebd5b3e135c1f66c0658ca4a75c650b9a
Author: jacekszymanski <ja...@gmail.com>
AuthorDate: Tue May 2 14:53:15 2023 +0200
CAMEL-19304: camel-jpa implement paging (#9970)
* refactor(test): extract non-test methods from AbstractJpaMethodTest
* feat: firstResult in query
* test: test firstResult
* support both maximumResults and firstResult in headers
* test paging
* use getHeader() with default value instead of Object.requireNonNullElse
---
.../camel/component/jpa/JpaEndpointConfigurer.java | 6 +
.../camel/component/jpa/JpaEndpointUriFactory.java | 3 +-
.../org/apache/camel/component/jpa/jpa.json | 5 +-
.../apache/camel/component/jpa/JpaConstants.java | 6 +
.../apache/camel/component/jpa/JpaEndpoint.java | 13 ++
.../apache/camel/component/jpa/JpaProducer.java | 12 +-
.../component/jpa/AbstractJpaMethodSupport.java | 92 ++++++++++
.../camel/component/jpa/AbstractJpaMethodTest.java | 63 +------
.../apache/camel/component/jpa/JpaPagingTest.java | 199 +++++++++++++++++++++
9 files changed, 334 insertions(+), 65 deletions(-)
diff --git a/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointConfigurer.java b/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointConfigurer.java
index f0bd06ea639..a5642ebc6cf 100644
--- a/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointConfigurer.java
+++ b/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointConfigurer.java
@@ -44,6 +44,8 @@ public class JpaEndpointConfigurer extends PropertyConfigurerSupport implements
case "exchangePattern": target.setExchangePattern(property(camelContext, org.apache.camel.ExchangePattern.class, value)); return true;
case "findentity":
case "findEntity": target.setFindEntity(property(camelContext, boolean.class, value)); return true;
+ case "firstresult":
+ case "firstResult": target.setFirstResult(property(camelContext, int.class, value)); return true;
case "flushonsend":
case "flushOnSend": target.setFlushOnSend(property(camelContext, boolean.class, value)); return true;
case "greedy": target.setGreedy(property(camelContext, boolean.class, value)); return true;
@@ -132,6 +134,8 @@ public class JpaEndpointConfigurer extends PropertyConfigurerSupport implements
case "exchangePattern": return org.apache.camel.ExchangePattern.class;
case "findentity":
case "findEntity": return boolean.class;
+ case "firstresult":
+ case "firstResult": return int.class;
case "flushonsend":
case "flushOnSend": return boolean.class;
case "greedy": return boolean.class;
@@ -221,6 +225,8 @@ public class JpaEndpointConfigurer extends PropertyConfigurerSupport implements
case "exchangePattern": return target.getExchangePattern();
case "findentity":
case "findEntity": return target.isFindEntity();
+ case "firstresult":
+ case "firstResult": return target.getFirstResult();
case "flushonsend":
case "flushOnSend": return target.isFlushOnSend();
case "greedy": return target.isGreedy();
diff --git a/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointUriFactory.java b/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointUriFactory.java
index 5522644910a..7848eb3859a 100644
--- a/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointUriFactory.java
+++ b/components/camel-jpa/src/generated/java/org/apache/camel/component/jpa/JpaEndpointUriFactory.java
@@ -21,7 +21,7 @@ public class JpaEndpointUriFactory extends org.apache.camel.support.component.En
private static final Set<String> SECRET_PROPERTY_NAMES;
private static final Set<String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(45);
+ Set<String> props = new HashSet<>(46);
props.add("backoffErrorThreshold");
props.add("backoffIdleThreshold");
props.add("backoffMultiplier");
@@ -35,6 +35,7 @@ public class JpaEndpointUriFactory extends org.apache.camel.support.component.En
props.add("exceptionHandler");
props.add("exchangePattern");
props.add("findEntity");
+ props.add("firstResult");
props.add("flushOnSend");
props.add("greedy");
props.add("initialDelay");
diff --git a/components/camel-jpa/src/generated/resources/org/apache/camel/component/jpa/jpa.json b/components/camel-jpa/src/generated/resources/org/apache/camel/component/jpa/jpa.json
index 93e8d7a4c16..8ea5e470d86 100644
--- a/components/camel-jpa/src/generated/resources/org/apache/camel/component/jpa/jpa.json
+++ b/components/camel-jpa/src/generated/resources/org/apache/camel/component/jpa/jpa.json
@@ -34,7 +34,9 @@
},
"headers": {
"CamelEntityManager": { "kind": "header", "displayName": "", "group": "common", "label": "", "required": false, "javaType": "jakarta.persistence.EntityManager", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The JPA EntityManager object.", "constantName": "org.apache.camel.component.jpa.JpaConstants#ENTITY_MANAGER" },
- "CamelJpaParameters": { "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Map<String, Object>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Alternative way for passing query parameters as an Exchange header.", "constantName": "org.apache.camel.component.jpa.JpaConstants#JPA_PARAMETERS_HEADER" }
+ "CamelJpaParameters": { "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "Map<String, Object>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Alternative way for passing query parameters as an Exchange header.", "constantName": "org.apache.camel.component.jpa.JpaConstants#JPA_PARAMETERS_HEADER" },
+ "CamelJpaMaximumResults": { "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Defines the maximum number of results to retrieve on the query; takes precedence over the value set on the endpoint, if any.", "constantName": "org.apache.camel.component.jpa.JpaConstants#JPA_MAXIMUM_RESULTS" },
+ "CamelJpaFirstResult": { "kind": "header", "displayName": "", "group": "producer", "label": "producer", "required": false, "javaType": "", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Defines the position of the first result to retrieve; takes precedence over the value set on the endpoint, if any.", "constantName": "org.apache.camel.component.jpa.JpaConstants#JPA_FIRST_RESULT" }
},
"properties": {
"entityType": { "kind": "path", "displayName": "Entity Type", "group": "common", "label": "", "required": true, "type": "string", "javaType": "java.lang.Class<java.lang.Object>", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Entity class name" },
@@ -60,6 +62,7 @@
"parameters": { "kind": "parameter", "displayName": "Parameters", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "java.util.Map<java.lang.String, java.lang.Object>", "prefix": "parameters.", "multiValue": true, "deprecated": false, "autowired": false, "secret": false, "description": "This key\/value mapping is used for building the query parameters. It is expected to be of the generic type java.util.Map where the keys ar [...]
"pollStrategy": { "kind": "parameter", "displayName": "Poll Strategy", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.PollingConsumerPollStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "A pluggable org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your custom implementation to control error handling usually occurred during the poll operation [...]
"findEntity": { "kind": "parameter", "displayName": "Find Entity", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled then the producer will find a single entity by using the message body as key and entityType as the class type. This can be used instead of a query to find a single entity." },
+ "firstResult": { "kind": "parameter", "displayName": "First Result", "group": "producer", "label": "producer", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "defaultValue": -1, "description": "Set the position of the first result to retrieve." },
"flushOnSend": { "kind": "parameter", "displayName": "Flush On Send", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Flushes the EntityManager after the entity bean has been persisted." },
"remove": { "kind": "parameter", "displayName": "Remove", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Indicates to use entityManager.remove(entity)." },
"useExecuteUpdate": { "kind": "parameter", "displayName": "Use Execute Update", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "description": "To configure whether to use executeUpdate() when producer executes a query. When you use INSERT, UPDATE or DELETE statement as a named query, you need to specify this option to 'true'." },
diff --git a/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaConstants.java b/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaConstants.java
index cf60c41e768..ad0c761777e 100644
--- a/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaConstants.java
+++ b/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaConstants.java
@@ -28,6 +28,12 @@ public final class JpaConstants {
@Metadata(label = "producer", description = "Alternative way for passing query parameters as an Exchange header.",
javaType = "Map<String, Object>")
public static final String JPA_PARAMETERS_HEADER = "CamelJpaParameters";
+ @Metadata(label = "producer", description = "Defines the maximum number of results to retrieve on the query; " +
+ "takes precedence over the value set on the endpoint, if any.")
+ public static final String JPA_MAXIMUM_RESULTS = "CamelJpaMaximumResults";
+ @Metadata(label = "producer", description = "Defines the position of the first result to retrieve; " +
+ "takes precedence over the value set on the endpoint, if any.")
+ public static final String JPA_FIRST_RESULT = "CamelJpaFirstResult";
/**
* @deprecated use {@link #ENTITY_MANAGER}
diff --git a/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaEndpoint.java b/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaEndpoint.java
index 1482ac7c015..992385285c7 100644
--- a/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaEndpoint.java
+++ b/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaEndpoint.java
@@ -70,6 +70,8 @@ public class JpaEndpoint extends ScheduledPollEndpoint {
private boolean sharedEntityManager;
@UriParam(defaultValue = "-1")
private int maximumResults = -1;
+ @UriParam(label = "producer", defaultValue = "-1")
+ private int firstResult = -1;
@UriParam(label = "consumer", defaultValue = "true")
private boolean consumeDelete = true;
@UriParam(label = "consumer", defaultValue = "true")
@@ -209,6 +211,17 @@ public class JpaEndpoint extends ScheduledPollEndpoint {
this.maximumResults = maximumResults;
}
+ public int getFirstResult() {
+ return firstResult;
+ }
+
+ /**
+ * Set the position of the first result to retrieve.
+ */
+ public void setFirstResult(int firstResult) {
+ this.firstResult = firstResult;
+ }
+
public Class<?> getEntityType() {
return entityType;
}
diff --git a/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaProducer.java b/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaProducer.java
index 3b039b649a1..7cee7c0f25c 100644
--- a/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaProducer.java
+++ b/components/camel-jpa/src/main/java/org/apache/camel/component/jpa/JpaProducer.java
@@ -202,10 +202,20 @@ public class JpaProducer extends DefaultProducer {
@SuppressWarnings("unchecked")
private void configureParameters(Query query, Exchange exchange) {
- int maxResults = getEndpoint().getMaximumResults();
+ final int maxResults = exchange.getIn().getHeader(
+ JpaConstants.JPA_MAXIMUM_RESULTS,
+ getEndpoint().getMaximumResults(),
+ Integer.class);
if (maxResults > 0) {
query.setMaxResults(maxResults);
}
+ final int firstResult = exchange.getIn().getHeader(
+ JpaConstants.JPA_FIRST_RESULT,
+ getEndpoint().getFirstResult(),
+ Integer.class);
+ if (firstResult > 0) {
+ query.setFirstResult(firstResult);
+ }
// setup the parameters
Map<String, ?> params;
if (parameters != null) {
diff --git a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AbstractJpaMethodSupport.java b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AbstractJpaMethodSupport.java
new file mode 100644
index 00000000000..dbd70b3b4dd
--- /dev/null
+++ b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AbstractJpaMethodSupport.java
@@ -0,0 +1,92 @@
+/*
+ * 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.apache.camel.component.jpa;
+
+import java.util.List;
+
+import jakarta.persistence.EntityManager;
+
+import org.apache.camel.Consumer;
+import org.apache.camel.examples.Address;
+import org.apache.camel.examples.Customer;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.AfterEach;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class AbstractJpaMethodSupport extends CamelTestSupport {
+
+ protected JpaEndpoint endpoint;
+ protected EntityManager entityManager;
+ protected TransactionTemplate transactionTemplate;
+ protected Consumer consumer;
+
+ @Override
+ @AfterEach
+ public void tearDown() {
+ if (entityManager != null) {
+ entityManager.close();
+ }
+ }
+
+ protected void setUp(String endpointUri) throws Exception {
+ endpoint = context.getEndpoint(endpointUri, JpaEndpoint.class);
+
+ transactionTemplate = endpoint.createTransactionTemplate();
+ entityManager = endpoint.getEntityManagerFactory().createEntityManager();
+
+ transactionTemplate.execute(new TransactionCallback<Object>() {
+ public Object doInTransaction(TransactionStatus status) {
+ entityManager.joinTransaction();
+ entityManager.createQuery("delete from " + Customer.class.getName()).executeUpdate();
+ return null;
+ }
+ });
+
+ assertEntitiesInDatabase(0, Customer.class.getName());
+ assertEntitiesInDatabase(0, Address.class.getName());
+ }
+
+ protected void save(final Object persistable) {
+ transactionTemplate.execute(new TransactionCallback<Object>() {
+ public Object doInTransaction(TransactionStatus status) {
+ entityManager.joinTransaction();
+ entityManager.persist(persistable);
+ entityManager.flush();
+ return null;
+ }
+ });
+ }
+
+ protected void assertEntitiesInDatabase(int count, String entity) {
+ List<?> results = entityManager.createQuery("select o from " + entity + " o").getResultList();
+ assertEquals(count, results.size());
+ }
+
+ protected Customer createDefaultCustomer() {
+ Customer customer = new Customer();
+ customer.setName("Christian Mueller");
+ Address address = new Address();
+ address.setAddressLine1("Hahnstr. 1");
+ address.setAddressLine2("60313 Frankfurt am Main");
+ customer.setAddress(address);
+ return customer;
+ }
+}
diff --git a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AbstractJpaMethodTest.java b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AbstractJpaMethodTest.java
index 019a37f1e5d..a07f51b1169 100644
--- a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AbstractJpaMethodTest.java
+++ b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AbstractJpaMethodTest.java
@@ -23,40 +23,22 @@ import java.util.concurrent.TimeUnit;
import jakarta.persistence.EntityManager;
-import org.apache.camel.Consumer;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.examples.Address;
import org.apache.camel.examples.Customer;
-import org.apache.camel.test.junit5.CamelTestSupport;
-import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
-import org.springframework.transaction.TransactionStatus;
-import org.springframework.transaction.support.TransactionCallback;
-import org.springframework.transaction.support.TransactionTemplate;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
-public abstract class AbstractJpaMethodTest extends CamelTestSupport {
+public abstract class AbstractJpaMethodTest extends AbstractJpaMethodSupport {
- protected JpaEndpoint endpoint;
- protected EntityManager entityManager;
- protected TransactionTemplate transactionTemplate;
- protected Consumer consumer;
protected Customer receivedCustomer;
abstract boolean usePersist();
- @Override
- @AfterEach
- public void tearDown() {
- if (entityManager != null) {
- entityManager.close();
- }
- }
-
@Test
public void produceNewEntity() throws Exception {
setUp("jpa://" + Customer.class.getName() + "?usePersist=" + (usePersist() ? "true" : "false"));
@@ -149,47 +131,4 @@ public abstract class AbstractJpaMethodTest extends CamelTestSupport {
assertEntitiesInDatabase(0, Address.class.getName());
}
- protected void setUp(String endpointUri) throws Exception {
- endpoint = context.getEndpoint(endpointUri, JpaEndpoint.class);
-
- transactionTemplate = endpoint.createTransactionTemplate();
- entityManager = endpoint.getEntityManagerFactory().createEntityManager();
-
- transactionTemplate.execute(new TransactionCallback<Object>() {
- public Object doInTransaction(TransactionStatus status) {
- entityManager.joinTransaction();
- entityManager.createQuery("delete from " + Customer.class.getName()).executeUpdate();
- return null;
- }
- });
-
- assertEntitiesInDatabase(0, Customer.class.getName());
- assertEntitiesInDatabase(0, Address.class.getName());
- }
-
- protected void save(final Object persistable) {
- transactionTemplate.execute(new TransactionCallback<Object>() {
- public Object doInTransaction(TransactionStatus status) {
- entityManager.joinTransaction();
- entityManager.persist(persistable);
- entityManager.flush();
- return null;
- }
- });
- }
-
- protected void assertEntitiesInDatabase(int count, String entity) {
- List<?> results = entityManager.createQuery("select o from " + entity + " o").getResultList();
- assertEquals(count, results.size());
- }
-
- protected Customer createDefaultCustomer() {
- Customer customer = new Customer();
- customer.setName("Christian Mueller");
- Address address = new Address();
- address.setAddressLine1("Hahnstr. 1");
- address.setAddressLine2("60313 Frankfurt am Main");
- customer.setAddress(address);
- return customer;
- }
}
diff --git a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaPagingTest.java b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaPagingTest.java
new file mode 100644
index 00000000000..824c9429c53
--- /dev/null
+++ b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaPagingTest.java
@@ -0,0 +1,199 @@
+/*
+ * 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.apache.camel.component.jpa;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.AnnotatedElement;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.IntStream;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.examples.Customer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class JpaPagingTest extends AbstractJpaMethodSupport {
+
+ private static final String ENDPOINT_URI = "jpa://" + Customer.class.getName() +
+ "?query=select c from Customer c order by c.name";
+
+ // should be less than 1000 as numbers in entries' names are formatted for sorting with %03d (or change format)
+ private static final int ENTRIES_COUNT = 30;
+
+ private static final String ENTRY_SEQ_FORMAT = "%03d";
+
+ // both should be less than ENTRIES_COUNT / 2
+ private static final int FIRST_RESULT = 5;
+ private static final int MAXIMUM_RESULTS = 10;
+
+ protected String additionalQueryParameters = "";
+
+ @Test
+ public void testUnrestrictedQueryReturnsAll() throws Exception {
+ final List<Customer> customers = runQueryTest();
+
+ assertEquals(ENTRIES_COUNT, customers.size());
+ }
+
+ @Test
+ @AdditionalQueryParameters("firstResult=" + FIRST_RESULT)
+ public void testFirstResultInUri() throws Exception {
+ final List<Customer> customers = runQueryTest();
+
+ assertEquals(ENTRIES_COUNT - FIRST_RESULT, customers.size());
+ }
+
+ @Test
+ public void testMaxResultsInHeader() throws Exception {
+ final List<Customer> customers
+ = runQueryTest(exchange -> exchange.getIn().setHeader(JpaConstants.JPA_MAXIMUM_RESULTS, MAXIMUM_RESULTS));
+
+ assertEquals(MAXIMUM_RESULTS, customers.size());
+ }
+
+ @Test
+ @AdditionalQueryParameters("maximumResults=" + MAXIMUM_RESULTS)
+ public void testFirstInHeaderMaxInUri() throws Exception {
+ final List<Customer> customers = runQueryTest(
+ withHeader(JpaConstants.JPA_FIRST_RESULT, FIRST_RESULT));
+
+ assertEquals(MAXIMUM_RESULTS, customers.size());
+ assertFirstCustomerSequence(customers, FIRST_RESULT);
+ }
+
+ @Test
+ @AdditionalQueryParameters("maximumResults=" + MAXIMUM_RESULTS)
+ public void testMaxHeaderPrevailsOverUri() throws Exception {
+ final List<Customer> customers = runQueryTest(
+ withHeader(JpaConstants.JPA_MAXIMUM_RESULTS, MAXIMUM_RESULTS * 2));
+
+ assertEquals(MAXIMUM_RESULTS * 2, customers.size());
+ }
+
+ @Test
+ @AdditionalQueryParameters("firstResult=" + FIRST_RESULT)
+ public void testFirstHeaderPrevailsOverUri() throws Exception {
+ final List<Customer> customers = runQueryTest(
+ withHeader(JpaConstants.JPA_FIRST_RESULT, FIRST_RESULT * 2));
+
+ assertEquals(ENTRIES_COUNT - (FIRST_RESULT * 2), customers.size());
+ assertFirstCustomerSequence(customers, FIRST_RESULT * 2);
+ }
+
+ @Test
+ public void testBothInHeader() throws Exception {
+ final List<Customer> customers = runQueryTest(
+ withHeader(JpaConstants.JPA_FIRST_RESULT, FIRST_RESULT),
+ withHeader(JpaConstants.JPA_MAXIMUM_RESULTS, MAXIMUM_RESULTS));
+
+ assertEquals(MAXIMUM_RESULTS, customers.size());
+ assertFirstCustomerSequence(customers, FIRST_RESULT);
+ }
+
+ @Test
+ @AdditionalQueryParameters("firstResult=" + ENTRIES_COUNT)
+ public void testFirstResultAfterTheEnd() throws Exception {
+ final List<Customer> customers = runQueryTest();
+
+ assertEquals(0, customers.size());
+ }
+
+ private static void assertFirstCustomerSequence(final List<Customer> customers, final int firstResult) {
+ assertTrue(customers.get(0).getName().endsWith(String.format(ENTRY_SEQ_FORMAT, firstResult)));
+ }
+
+ @SuppressWarnings("unchecked")
+ protected List<Customer> runQueryTest(final Processor... preRun) throws Exception {
+ setUp(getEndpointUri());
+
+ final Exchange result = template.send("direct:start", exchange -> {
+ for (Processor processor : preRun) {
+ processor.process(exchange);
+ }
+ });
+
+ return (List<Customer>) result.getMessage().getBody(List.class);
+ }
+
+ @Override
+ public void beforeEach(ExtensionContext context) throws Exception {
+ super.beforeEach(context);
+
+ final Optional<AnnotatedElement> element = context.getElement();
+
+ if (element.isPresent()) {
+ final AnnotatedElement annotatedElement = element.get();
+ final AdditionalQueryParameters annotation = annotatedElement.getAnnotation(AdditionalQueryParameters.class);
+ if (annotation != null && !annotation.value().isBlank()) {
+ additionalQueryParameters = annotation.value();
+ }
+ }
+ }
+
+ @Override
+ protected void setUp(String endpointUri) throws Exception {
+ super.setUp(endpointUri);
+
+ createCustomers();
+ assertEntitiesInDatabase(ENTRIES_COUNT, Customer.class.getName());
+ }
+
+ protected void createCustomers() {
+ IntStream.range(0, ENTRIES_COUNT).forEach(idx -> {
+ Customer customer = createDefaultCustomer();
+ customer.setName(String.format("%s " + ENTRY_SEQ_FORMAT, customer.getName(), idx));
+ save(customer);
+ });
+ }
+
+ @Override
+ protected RoutesBuilder createRouteBuilder() throws Exception {
+ final String endpointUri = getEndpointUri();
+ return new RouteBuilder() {
+ public void configure() {
+ from("direct:start")
+ .to(endpointUri);
+ }
+ };
+ }
+
+ protected String getEndpointUri() {
+ return ENDPOINT_URI +
+ (additionalQueryParameters.isBlank() ? "" : "&" + additionalQueryParameters);
+ }
+
+ protected Processor withHeader(final String headerName, final Object headerValue) {
+ return exchange -> exchange.getIn().setHeader(headerName, headerValue);
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ ElementType.METHOD })
+ private @interface AdditionalQueryParameters {
+ String value();
+ }
+
+}