This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch camel-3.x in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-3.x by this push: new 55caf5d03a2 CAMEL-19327: camel-jpa support singleResult option (#10032) 55caf5d03a2 is described below commit 55caf5d03a24be0a447154d2443d7dcd3f30b236 Author: jacekszymanski <jacek.p.szyman...@gmail.com> AuthorDate: Wed May 10 06:24:55 2023 +0200 CAMEL-19327: camel-jpa support singleResult option (#10032) * support outputType JpaEndpoint option * AdditionalQueryParameters no longer nested * create JpaWithOptionsTestSupport, move support methods from JpaPagingTest * outputType Query tests * refactor tests * add outputType support to processFind * test outputType on finds * refactor, the option is now boolean, named singleResult --- .../camel/component/jpa/JpaEndpointConfigurer.java | 6 + .../camel/component/jpa/JpaEndpointUriFactory.java | 3 +- .../org/apache/camel/component/jpa/jpa.json | 1 + .../apache/camel/component/jpa/JpaEndpoint.java | 14 +++ .../apache/camel/component/jpa/JpaProducer.java | 19 ++- .../component/jpa/AdditionalQueryParameters.java | 30 +++++ .../camel/component/jpa/JpaOutputTypeTest.java | 140 +++++++++++++++++++++ .../apache/camel/component/jpa/JpaPagingTest.java | 105 ++-------------- .../component/jpa/JpaWithOptionsTestSupport.java | 119 ++++++++++++++++++ 9 files changed, 341 insertions(+), 96 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 62e856b71f2..8155acd62a5 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 @@ -89,6 +89,8 @@ public class JpaEndpointConfigurer extends PropertyConfigurerSupport implements case "sendEmptyMessageWhenIdle": target.setSendEmptyMessageWhenIdle(property(camelContext, boolean.class, value)); return true; case "sharedentitymanager": case "sharedEntityManager": target.setSharedEntityManager(property(camelContext, boolean.class, value)); return true; + case "singleresult": + case "singleResult": target.setSingleResult(property(camelContext, boolean.class, value)); return true; case "skiplockedentity": case "skipLockedEntity": target.setSkipLockedEntity(property(camelContext, boolean.class, value)); return true; case "startscheduler": @@ -179,6 +181,8 @@ public class JpaEndpointConfigurer extends PropertyConfigurerSupport implements case "sendEmptyMessageWhenIdle": return boolean.class; case "sharedentitymanager": case "sharedEntityManager": return boolean.class; + case "singleresult": + case "singleResult": return boolean.class; case "skiplockedentity": case "skipLockedEntity": return boolean.class; case "startscheduler": @@ -270,6 +274,8 @@ public class JpaEndpointConfigurer extends PropertyConfigurerSupport implements case "sendEmptyMessageWhenIdle": return target.isSendEmptyMessageWhenIdle(); case "sharedentitymanager": case "sharedEntityManager": return target.isSharedEntityManager(); + case "singleresult": + case "singleResult": return target.isSingleResult(); case "skiplockedentity": case "skipLockedEntity": return target.isSkipLockedEntity(); case "startscheduler": 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 7848eb3859a..7c748ec540a 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<>(46); + Set<String> props = new HashSet<>(47); props.add("backoffErrorThreshold"); props.add("backoffIdleThreshold"); props.add("backoffMultiplier"); @@ -60,6 +60,7 @@ public class JpaEndpointUriFactory extends org.apache.camel.support.component.En props.add("schedulerProperties"); props.add("sendEmptyMessageWhenIdle"); props.add("sharedEntityManager"); + props.add("singleResult"); props.add("skipLockedEntity"); props.add("startScheduler"); props.add("timeUnit"); 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 4c490ed1530..ced7dfba7aa 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 @@ -65,6 +65,7 @@ "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)." }, + "singleResult": { "kind": "parameter", "displayName": "Single Result", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If enabled, a query or a find which would return no results or more than one result, will throw an exception instead." }, "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'." }, "usePersist": { "kind": "parameter", "displayName": "Use Persist", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Indicates to use entityManager.persist(entity) instead of entityManager.merge(entity). Note: entityManager.persist(entity) doesn't work for detached entities (where the EntityManager has to execute an UPDATE instead of an [...] "lazyStartProducer": { "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may other [...] 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 46998eec4a2..d0041b49412 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 @@ -112,6 +112,8 @@ public class JpaEndpoint extends ScheduledPollEndpoint { private Boolean useExecuteUpdate; @UriParam(label = "producer") private boolean findEntity; + @UriParam(label = "producer", defaultValue = "false") + private boolean singleResult; @UriParam(label = "advanced", prefix = "emf.", multiValue = true) private Map<String, Object> entityManagerProperties; @@ -557,6 +559,18 @@ public class JpaEndpoint extends ScheduledPollEndpoint { this.findEntity = findEntity; } + public boolean isSingleResult() { + return singleResult; + } + + /** + * If enabled, a query or a find which would return no results or more than one result, will throw an exception + * instead. + */ + public void setSingleResult(boolean singleResult) { + this.singleResult = singleResult; + } + // Implementation methods // ------------------------------------------------------------------------- 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 43287bf7766..1bf4a6cdbc5 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 @@ -22,6 +22,7 @@ import java.util.Map; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; +import javax.persistence.NoResultException; import javax.persistence.Query; import org.apache.camel.Exchange; @@ -189,7 +190,16 @@ public class JpaProducer extends DefaultProducer { } else { target = exchange.getIn(); } - Object answer = isUseExecuteUpdate() ? innerQuery.executeUpdate() : innerQuery.getResultList(); + + final Object answer; + if (isUseExecuteUpdate()) { + answer = innerQuery.executeUpdate(); + } else if (getEndpoint().isSingleResult()) { + answer = innerQuery.getSingleResult(); + } else { + answer = innerQuery.getResultList(); + } + target.setBody(answer); if (getEndpoint().isFlushOnSend()) { @@ -244,6 +254,13 @@ public class JpaProducer extends DefaultProducer { Object answer = entityManager.find(getEndpoint().getEntityType(), key); LOG.debug("Find: {} -> {}", key, answer); + if (getEndpoint().isSingleResult() && answer == null) { + throw new NoResultException( + String.format( + "No results for key %s and singleResult requested", + key)); + } + Message target; if (ExchangeHelper.isOutCapable(exchange)) { target = exchange.getMessage(); diff --git a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AdditionalQueryParameters.java b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AdditionalQueryParameters.java new file mode 100644 index 00000000000..d966268b4b2 --- /dev/null +++ b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/AdditionalQueryParameters.java @@ -0,0 +1,30 @@ +/* + * 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; + +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = { ElementType.METHOD }) +@interface AdditionalQueryParameters { + + String value(); + +} diff --git a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaOutputTypeTest.java b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaOutputTypeTest.java new file mode 100644 index 00000000000..25ae0e299e8 --- /dev/null +++ b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaOutputTypeTest.java @@ -0,0 +1,140 @@ +/* + * 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 javax.persistence.NoResultException; +import javax.persistence.NonUniqueResultException; + +import org.apache.camel.Exchange; +import org.apache.camel.examples.Customer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class JpaOutputTypeTest extends JpaWithOptionsTestSupport { + + private static final String ENDPOINT_URI = "jpa://" + Customer.class.getName(); + + private String queryOrFind; + + @Test + @Query + @AdditionalQueryParameters("singleResult=true¶meters.seq=% 001") + public void testSingleCustomerOKQuery() throws Exception { + final Customer customer = runQueryTest(Customer.class); + + assertNotNull(customer); + } + + @Test + @Query("select c from Customer c") + @AdditionalQueryParameters("singleResult=true") + public void testTooMuchResults() throws Exception { + final Exchange result = doRunQueryTest(); + + Assertions.assertInstanceOf(NonUniqueResultException.class, getException(result)); + } + + @Test + @Query + @AdditionalQueryParameters("singleResult=true¶meters.seq=% xxx") + public void testNoCustomersQuery() throws Exception { + final Exchange result = doRunQueryTest(); + + Assertions.assertInstanceOf(NoResultException.class, getException(result)); + } + + @Test + @Find + @AdditionalQueryParameters("singleResult=true") + public void testSingleCustomerOKFind() throws Exception { + // ids in the db are not known, so query for a known element and use its id. + super.setUp(getEndpointUri()); + + final Customer fromDb = (Customer) entityManager + .createQuery("select c from Customer c where c.name like '% 001'") + .getSingleResult(); + + final Exchange result = template.send("direct:start", withBody(fromDb.getId())); + + assertNotNull(result.getIn().getBody(Customer.class)); + } + + @Test + @Find + @AdditionalQueryParameters("singleResult=true") + public void testNoCustomerFind() throws Exception { + final Exchange result = doRunQueryTest(withBody(Long.MAX_VALUE)); + + Assertions.assertInstanceOf(NoResultException.class, getException(result)); + } + + @Override + protected String getEndpointUri() { + return String.format("%s?%s%s", + ENDPOINT_URI, + queryOrFind, + createAdditionalQueryParameters()); + } + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + super.beforeEach(context); + + // a query or a find is necessary - without the annotation test can't continue + final AnnotatedElement annotatedElement = context.getElement().get(); + + final Find find = annotatedElement.getAnnotation(Find.class); + final Query query = annotatedElement.getAnnotation(Query.class); + + if ((find == null) == (query == null)) { + throw new IllegalStateException("Test must be annotated with EITHER Find OR Query"); + } + + if (find != null) { + queryOrFind = "findEntity=" + true; + } else { // query != null + queryOrFind = "query=" + query.value(); + } + + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + private @interface Find { + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + private @interface Query { + String value() default "select c from Customer c where c.name like :seq"; + } + + private static Exception getException(final Exchange exchange) { + final Exception exception = exchange.getException(); + + return exception != null ? exception : exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class); + } +} 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 index 824c9429c53..32aaf4d3d1a 100644 --- 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 @@ -16,47 +16,28 @@ */ 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 { +public class JpaPagingTest extends JpaWithOptionsTestSupport { 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 + // both should be less than JpaWithOptionsTestSupport.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()); + assertEquals(JpaWithOptionsTestSupport.ENTRIES_COUNT, customers.size()); } @Test @@ -64,13 +45,13 @@ public class JpaPagingTest extends AbstractJpaMethodSupport { public void testFirstResultInUri() throws Exception { final List<Customer> customers = runQueryTest(); - assertEquals(ENTRIES_COUNT - FIRST_RESULT, customers.size()); + assertEquals(JpaWithOptionsTestSupport.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)); + final List<Customer> customers = runQueryTest( + withHeader(JpaConstants.JPA_MAXIMUM_RESULTS, MAXIMUM_RESULTS)); assertEquals(MAXIMUM_RESULTS, customers.size()); } @@ -100,7 +81,7 @@ public class JpaPagingTest extends AbstractJpaMethodSupport { final List<Customer> customers = runQueryTest( withHeader(JpaConstants.JPA_FIRST_RESULT, FIRST_RESULT * 2)); - assertEquals(ENTRIES_COUNT - (FIRST_RESULT * 2), customers.size()); + assertEquals(JpaWithOptionsTestSupport.ENTRIES_COUNT - (FIRST_RESULT * 2), customers.size()); assertFirstCustomerSequence(customers, FIRST_RESULT * 2); } @@ -115,7 +96,7 @@ public class JpaPagingTest extends AbstractJpaMethodSupport { } @Test - @AdditionalQueryParameters("firstResult=" + ENTRIES_COUNT) + @AdditionalQueryParameters("firstResult=" + JpaWithOptionsTestSupport.ENTRIES_COUNT) public void testFirstResultAfterTheEnd() throws Exception { final List<Customer> customers = runQueryTest(); @@ -123,77 +104,13 @@ public class JpaPagingTest extends AbstractJpaMethodSupport { } 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); - } - }; + assertTrue(customers.get(0).getName().endsWith( + String.format(JpaWithOptionsTestSupport.ENTRY_SEQ_FORMAT, firstResult))); } 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(); + createAdditionalQueryParameters(); } } diff --git a/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaWithOptionsTestSupport.java b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaWithOptionsTestSupport.java new file mode 100644 index 00000000000..0394799f362 --- /dev/null +++ b/components/camel-jpa/src/test/java/org/apache/camel/component/jpa/JpaWithOptionsTestSupport.java @@ -0,0 +1,119 @@ +/* + * 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.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.Assertions; +import org.junit.jupiter.api.extension.ExtensionContext; + +public abstract class JpaWithOptionsTestSupport extends AbstractJpaMethodSupport { + + // should be less than 1000 as numbers in entries' names are formatted for sorting with %03d (or change format) + static final int ENTRIES_COUNT = 30; + static final String ENTRY_SEQ_FORMAT = "%03d"; + + private String additionalQueryParameters = ""; + + protected String createAdditionalQueryParameters() { + return additionalQueryParameters.isBlank() ? "" : "&" + additionalQueryParameters; + } + + @Override + protected RoutesBuilder createRouteBuilder() throws Exception { + final String endpointUri = getEndpointUri(); + return new RouteBuilder() { + public void configure() { + from("direct:start").to(endpointUri); + } + }; + } + + protected Exchange doRunQueryTest(final Processor... preRun) throws Exception { + setUp(getEndpointUri()); + + return template.send("direct:start", exchange -> { + for (Processor processor : preRun) { + processor.process(exchange); + } + }); + + } + + protected <E> E runQueryTest(Class<E> type, final Processor... preRun) throws Exception { + final Exchange result = doRunQueryTest(preRun); + + Assertions.assertNull(result.getException()); + Assertions.assertNull(result.getProperty(Exchange.EXCEPTION_CAUGHT)); + + return result.getMessage().getBody(type); + + } + + @SuppressWarnings(value = "unchecked") + protected List<Customer> runQueryTest(final Processor... preRun) throws Exception { + return (List<Customer>) runQueryTest(List.class, preRun); + } + + protected Processor withHeader(final String headerName, final Object headerValue) { + return exchange -> exchange.getIn().setHeader(headerName, headerValue); + } + + protected Processor withBody(final Object body) { + return exchange -> exchange.getIn().setBody(body); + } + + @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(); + } + } + } + + protected abstract String getEndpointUri(); + + 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 void setUp(String endpointUri) throws Exception { + super.setUp(endpointUri); + createCustomers(); + assertEntitiesInDatabase(ENTRIES_COUNT, Customer.class.getName()); + } + +}