This is an automated email from the ASF dual-hosted git repository. jamesnetherton pushed a commit to branch 3.20.x in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
The following commit(s) were added to refs/heads/3.20.x by this push: new c9674a96c6 Improve handling of findSingleByType where multiple beans exist without any @Default qualifier c9674a96c6 is described below commit c9674a96c6d0284c0338b05fae60ae9fba9fecb0 Author: Zheng Feng <zh.f...@gmail.com> AuthorDate: Tue May 20 14:05:29 2025 +0800 Improve handling of findSingleByType where multiple beans exist without any @Default qualifier Fixes #7315 Co-authored-by: James Netherton <jamesnether...@gmail.com> --- .../core/runtime/CamelBeanPrecedenceTest.java | 172 +++++++++++++++++++++ .../quarkus/core/runtime/CamelPrecedeBeanTest.java | 70 --------- .../camel/quarkus/core/RuntimeBeanRepository.java | 75 +++++---- .../component/jpa/deployment/JpaProcessor.java | 52 +++++++ .../quarkus/component/jpa/it/JpaResource.java | 35 +++++ .../camel/quarkus/component/jpa/it/JpaRoute.java | 2 + .../jpa/src/main/resources/application.properties | 33 +++- ...DefaultAndExplicitEntityManagerFactoryTest.java | 52 +++++++ ...JpaMultipleNamedResourcesWithNoDefaultTest.java | 46 ++++++ .../JpaSingleNamedResourceWithNoDefaultTest.java | 45 ++++++ .../quarkus/component/sql/it/SqlDbInitializer.java | 2 + .../quarkus/component/sql/it/SqlResource.java | 2 + .../camel/quarkus/component/sql/it/SqlRoutes.java | 2 + .../sql/it/datasource/SqlDataSourceResource.java | 64 ++++++++ .../sql/src/main/resources/application.properties | 19 ++- .../SqlMultipleNamedDatasourceWithDefaultTest.java | 58 +++++++ ...qlMultipleNamedDatasourceWithNoDefaultTest.java | 58 +++++++ 17 files changed, 682 insertions(+), 105 deletions(-) diff --git a/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/runtime/CamelBeanPrecedenceTest.java b/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/runtime/CamelBeanPrecedenceTest.java new file mode 100644 index 0000000000..31352e76e9 --- /dev/null +++ b/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/runtime/CamelBeanPrecedenceTest.java @@ -0,0 +1,172 @@ +/* + * 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.quarkus.core.runtime; + +import io.quarkus.arc.DefaultBean; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.common.annotation.Identifier; +import jakarta.annotation.Priority; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; +import org.apache.camel.CamelContext; +import org.apache.camel.support.CamelContextHelper; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class CamelBeanPrecedenceTest { + @RegisterExtension + static final QuarkusUnitTest CONFIG = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + @Inject + CamelContext context; + + @Test + public void findSingleByTypeForDefaultBeansWithPriority() { + BeanA bean = CamelContextHelper.findSingleByType(context, BeanA.class); + assertEquals("bar", bean.name()); + } + + @Test + public void findSingleByTypeForDefaultBeansWithSamePriority() { + BeanB bean = CamelContextHelper.findSingleByType(context, BeanB.class); + assertNull(bean); + } + + @Test + public void findSingleByTypeForDefaultBeansWithoutPriority() { + BeanC bean = CamelContextHelper.findSingleByType(context, BeanC.class); + assertNull(bean); + } + + @Test + public void findSingleByTypeForDefaultBean() { + BeanD bean = CamelContextHelper.findSingleByType(context, BeanD.class); + assertEquals("foo", bean.name()); + } + + @Test + public void findSingleByTypeWhereIdentifierQualifierAppliedToBean() { + BeanE bean = CamelContextHelper.findSingleByType(context, BeanE.class); + assertEquals("foo", bean.name()); + } + + @Test + public void findSingleByTypeWhereSingleBeanExists() { + BeanF bean = CamelContextHelper.findSingleByType(context, BeanF.class); + assertEquals("foo", bean.name()); + } + + @Test + public void findSingleByTypeWhereNoBeanProduced() { + BeanG bean = CamelContextHelper.findSingleByType(context, BeanG.class); + assertNull(bean); + } + + // Default beans with priority + @Produces + @Priority(100) + BeanA createFooBeanA() { + return new BeanA("foo"); + } + + @Produces + @Priority(200) + BeanA createBarBeanA() { + return new BeanA("bar"); + } + + // Default beans with same priority + @Produces + @Priority(100) + BeanB createFooBeanB() { + return new BeanB("foo"); + } + + @Produces + @Priority(100) + BeanB createBarBeanB() { + return new BeanB("bar"); + } + + // Multiple default beans without priority + @Produces + BeanC createFooBeanC() { + return new BeanC("foo"); + } + + @Produces + BeanC createBarBeanC() { + return new BeanC("bar"); + } + + // Multiple beans with DefaultBean override + @Produces + BeanD createFooBeanD() { + return new BeanD("foo"); + } + + @Produces + @DefaultBean + BeanD createBarBeanD() { + return new BeanD("bar"); + } + + // Multiple beans with @Identifier qualifier + @Produces + BeanE createFooBeanE() { + return new BeanE("foo"); + } + + @Produces + @Identifier("bar") + BeanE createBarBeanE() { + return new BeanE("bar"); + } + + // Single bean + @Produces + BeanF createFooBeanF() { + return new BeanF("foo"); + } + + public record BeanA(String name) { + } + + public record BeanB(String name) { + } + + public record BeanC(String name) { + } + + public record BeanD(String name) { + } + + public record BeanE(String name) { + } + + public record BeanF(String name) { + } + + public record BeanG(String name) { + } +} diff --git a/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/runtime/CamelPrecedeBeanTest.java b/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/runtime/CamelPrecedeBeanTest.java deleted file mode 100644 index 71620aa058..0000000000 --- a/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/runtime/CamelPrecedeBeanTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.quarkus.core.runtime; - -import io.quarkus.test.QuarkusUnitTest; -import jakarta.annotation.Priority; -import jakarta.enterprise.inject.Produces; -import jakarta.inject.Inject; -import org.apache.camel.CamelContext; -import org.apache.camel.support.CamelContextHelper; -import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class CamelPrecedeBeanTest { - @RegisterExtension - static final QuarkusUnitTest CONFIG = new QuarkusUnitTest() - .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addClasses(MyTestBean.class)); - - @Inject - CamelContext context; - - @Test - public void testBeanPrecedence() { - MyTestBean bean = CamelContextHelper.findSingleByType(context, MyTestBean.class); - assertEquals("bar", bean.getName()); - } - - @Produces - @Priority(100) - MyTestBean createFoo() { - return new MyTestBean("foo"); - } - - @Produces - @Priority(200) - MyTestBean createBar() { - return new MyTestBean("bar"); - } - - public static final class MyTestBean { - private final String name; - - public MyTestBean(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } -} diff --git a/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/RuntimeBeanRepository.java b/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/RuntimeBeanRepository.java index 6382cd7b0c..6eb36869b0 100644 --- a/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/RuntimeBeanRepository.java +++ b/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/RuntimeBeanRepository.java @@ -171,39 +171,58 @@ public final class RuntimeBeanRepository implements BeanRepository { public <T> T findSingleByType(Class<T> type) { ArcContainer container = Arc.container(); Optional<Annotation[]> qualifiers = resolveQualifiersForType(type); - if (container != null) { - List<InstanceHandle<T>> handles; - if (qualifiers.isPresent()) { - handles = container.listAll(type, qualifiers.get()); - } else { - handles = container.listAll(type); - } + List<InstanceHandle<T>> handles; + if (qualifiers.isPresent()) { + handles = container.listAll(type, qualifiers.get()); + } else { + handles = container.listAll(type); + } + + if (handles.isEmpty()) { + // No matches for the given bean type + return null; + } else if (handles.size() == 1) { + // Only 1 bean exists for the given type so just return it + return handles.get(0).get(); + } + // For multiple bean matches determine how many have the @Default qualifier + long defaultBeanCount = handles.stream() + .map(InstanceHandle::getBean) + .filter(this::isDefaultBean) + .count(); + + // Determine if all beans for the matching type have the same priority + boolean beansHaveSamePriority = handles.stream() + .map(InstanceHandle::getBean) + .map(InjectableBean::getPriority) + .distinct() + .count() == 1; + + // Try to resolve the target bean by @Priority and @Default qualifiers + if ((defaultBeanCount == 1) || (defaultBeanCount > 1 && !beansHaveSamePriority)) { List<InstanceHandle<T>> sortedHandles = new ArrayList<>(handles.size()); sortedHandles.addAll(handles); - - if (sortedHandles.size() > 1) { - sortedHandles.sort((bean1, bean2) -> { - Integer priority2 = bean2.getBean().getPriority(); - Integer priority1 = bean1.getBean().getPriority(); - - int result = priority2.compareTo(priority1); - // If the priority is same, the default bean wins - if (result == 0) { - if (isDefaultBean(bean1.getBean())) { - result = -1; - } else if (isDefaultBean(bean2.getBean())) { - result = 1; - } + sortedHandles.sort((bean1, bean2) -> { + Integer priority2 = bean2.getBean().getPriority(); + Integer priority1 = bean1.getBean().getPriority(); + + int result = priority2.compareTo(priority1); + // If the priority is same, the default bean wins + if (result == 0) { + if (isDefaultBean(bean1.getBean())) { + result = -1; + } else if (isDefaultBean(bean2.getBean())) { + result = 1; } - return result; - }); - } - - if (sortedHandles.size() > 0) { - return sortedHandles.get(0).get(); - } + } + return result; + }); + return sortedHandles.get(0).get(); } + + // Multiple beans exist for the given type, and we could not determine which one to use + // Users must resolve the conflict by explicitly referencing the bean via endpoint URI options etc return null; } diff --git a/extensions/jpa/deployment/src/main/java/org/apache/camel/quarkus/component/jpa/deployment/JpaProcessor.java b/extensions/jpa/deployment/src/main/java/org/apache/camel/quarkus/component/jpa/deployment/JpaProcessor.java index 4ec977b19d..9ba2e5ce22 100644 --- a/extensions/jpa/deployment/src/main/java/org/apache/camel/quarkus/component/jpa/deployment/JpaProcessor.java +++ b/extensions/jpa/deployment/src/main/java/org/apache/camel/quarkus/component/jpa/deployment/JpaProcessor.java @@ -23,14 +23,20 @@ import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.gizmo.Gizmo; import io.quarkus.hibernate.orm.deployment.PersistenceUnitDescriptorBuildItem; import jakarta.persistence.EntityManagerFactory; import org.apache.camel.component.jpa.JpaComponent; +import org.apache.camel.component.jpa.JpaEndpoint; import org.apache.camel.quarkus.component.jpa.CamelJpaProducer; import org.apache.camel.quarkus.component.jpa.CamelJpaRecorder; import org.apache.camel.quarkus.core.deployment.spi.CamelBeanQualifierResolverBuildItem; import org.apache.camel.quarkus.core.deployment.spi.CamelRuntimeBeanBuildItem; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; class JpaProcessor { @@ -75,4 +81,50 @@ class JpaProcessor { } } } + + // TODO: Remove this and make it possible to override methods in JpaEndpoint + // https://github.com/apache/camel-quarkus/issues/7369 + @BuildStep + BytecodeTransformerBuildItem transformMethodJpaEndpointCreateEntityManagerFactory() { + // Since spring-orm is not on the classpath, transform JpaEndpoint.createEntityManagerFactory + // to avoid trying to create a local LocalEntityManagerFactoryBean and + // suppress ClassNotFoundException when the component could not find a EntityManagerFactory to use. + // + // For example: + // + // protected EntityManagerFactory createEntityManagerFactory() { + // throw new IllegalStateException(); + // } + return new BytecodeTransformerBuildItem.Builder() + .setClassToTransform(JpaEndpoint.class.getName()) + .setCacheable(true) + .setVisitorFunction((className, classVisitor) -> { + return new ClassVisitor(Gizmo.ASM_API_VERSION, classVisitor) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, + String[] exceptions) { + MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions); + + if (name.equals("createEntityManagerFactory") + && descriptor.equals("()Ljakarta/persistence/EntityManagerFactory;")) { + return new MethodVisitor(Gizmo.ASM_API_VERSION, visitor) { + @Override + public void visitCode() { + super.visitCode(); + visitor.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException"); + visitor.visitInsn(Opcodes.DUP); + visitor.visitLdcInsn( + "Cannot create EntityManagerFactory. Check quarkus.hibernate-orm configuration."); + visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/IllegalStateException", + "<init>", "(Ljava/lang/String;)V", false); + visitor.visitInsn(Opcodes.ATHROW); + } + }; + } + return visitor; + } + }; + }) + .build(); + } } diff --git a/integration-tests/jpa/src/main/java/org/apache/camel/quarkus/component/jpa/it/JpaResource.java b/integration-tests/jpa/src/main/java/org/apache/camel/quarkus/component/jpa/it/JpaResource.java index 4dc0779571..762934284c 100644 --- a/integration-tests/jpa/src/main/java/org/apache/camel/quarkus/component/jpa/it/JpaResource.java +++ b/integration-tests/jpa/src/main/java/org/apache/camel/quarkus/component/jpa/it/JpaResource.java @@ -24,7 +24,12 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import io.quarkus.arc.ClientProxy; +import io.quarkus.arc.InjectableBean; +import jakarta.enterprise.inject.Default; import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.persistence.EntityManagerFactory; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.DELETE; import jakarta.ws.rs.GET; @@ -39,6 +44,7 @@ import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.ProducerTemplate; import org.apache.camel.component.jpa.JpaConstants; +import org.apache.camel.component.jpa.JpaEndpoint; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.quarkus.component.jpa.it.model.Fruit; @@ -134,4 +140,33 @@ public class JpaResource { mock.reset(); return Response.ok().build(); } + + @Path("/entityManagerFactory") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response createJpaEndpoint() { + try { + String uri = "jpa:" + Fruit.class.getName() + "?nativeQuery=SELECT * FROM fruits"; + + JpaEndpoint endpoint = context.getEndpoint(uri, JpaEndpoint.class); + EntityManagerFactory entityManagerFactory = endpoint.getEntityManagerFactory(); + + InjectableBean<?> bean = ((ClientProxy) entityManagerFactory).arc_bean(); + Named namedQualifier = (Named) bean.getQualifiers() + .stream() + .filter(qualifier -> qualifier.annotationType().equals(Named.class)) + .findFirst() + .orElse(null); + + String beanName = namedQualifier != null ? namedQualifier.value() : ""; + + Map<String, Object> response = Map.of( + "name", beanName, + "default", bean.getQualifiers().contains(Default.Literal.INSTANCE)); + + return Response.ok(response).build(); + } catch (Exception e) { + return Response.serverError().entity(e.getMessage()).build(); + } + } } diff --git a/integration-tests/jpa/src/main/java/org/apache/camel/quarkus/component/jpa/it/JpaRoute.java b/integration-tests/jpa/src/main/java/org/apache/camel/quarkus/component/jpa/it/JpaRoute.java index 85b396c3af..a4964d81f2 100644 --- a/integration-tests/jpa/src/main/java/org/apache/camel/quarkus/component/jpa/it/JpaRoute.java +++ b/integration-tests/jpa/src/main/java/org/apache/camel/quarkus/component/jpa/it/JpaRoute.java @@ -18,6 +18,7 @@ package org.apache.camel.quarkus.component.jpa.it; import java.util.Collections; +import io.quarkus.arc.profile.UnlessBuildProfile; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.inject.Named; @@ -27,6 +28,7 @@ import org.apache.camel.component.jpa.TransactionStrategy; import org.apache.camel.processor.idempotent.jpa.JpaMessageIdRepository; import org.apache.camel.quarkus.component.jpa.it.model.Fruit; +@UnlessBuildProfile(anyOf = { "single-resource-no-default", "multi-resource-no-default" }) @ApplicationScoped public class JpaRoute extends RouteBuilder { diff --git a/integration-tests/jpa/src/main/resources/application.properties b/integration-tests/jpa/src/main/resources/application.properties index 699598208b..b8a35ef880 100644 --- a/integration-tests/jpa/src/main/resources/application.properties +++ b/integration-tests/jpa/src/main/resources/application.properties @@ -14,11 +14,32 @@ ## See the License for the specific language governing permissions and ## limitations under the License. ## --------------------------------------------------------------------------- -quarkus.datasource.db-kind=${cq.sqlJdbcKind:h2} -quarkus.datasource.test.db-kind=${cq.sqlJdbcKind:h2} -quarkus.datasource.test.jdbc.max-size=8 +jpa.model.packages=org.apache.camel.quarkus.component.jpa.it.model,org.apache.camel.processor.idempotent.jpa -quarkus.hibernate-orm.test.packages=org.apache.camel.quarkus.component.jpa.it.model,org.apache.camel.processor.idempotent.jpa -quarkus.hibernate-orm.test.datasource=test -quarkus.hibernate-orm.test.database.generation=drop-and-create +%dev,test,prod.quarkus.datasource.db-kind=${cq.sqlJdbcKind:h2} + +%dev,test,prod.quarkus.datasource.test.db-kind=${cq.sqlJdbcKind:h2} +%dev,test,prod.quarkus.datasource.test.jdbc.max-size=8 + +%dev,test,prod.quarkus.hibernate-orm.test.packages=${jpa.model.packages} +%dev,test,prod.quarkus.hibernate-orm.test.datasource=test +%dev,test,prod.quarkus.hibernate-orm.test.database.generation=drop-and-create + +# single-resource-no-default profile to test single named DataSource / EntityManagerFactory beans without defaults +%single-resource-no-default.quarkus.camel.routes-discovery.exclude-patterns=**/* +%single-resource-no-default.quarkus.datasource.testA.db-kind=h2 +%single-resource-no-default.quarkus.hibernate-orm.testA.packages=${jpa.model.packages} +%single-resource-no-default.quarkus.hibernate-orm.testA.datasource=testA +%single-resource-no-default.quarkus.hibernate-orm.testA.database.generation=drop-and-create + +# multi-resource-no-default profile to test multiple named DataSource / EntityManagerFactory beans without defaults +%multi-resource-no-default.quarkus.camel.routes-discovery.exclude-patterns=**/* +%multi-resource-no-default.quarkus.datasource.testA.db-kind=h2 +%multi-resource-no-default.quarkus.hibernate-orm.testA.packages=${jpa.model.packages} +%multi-resource-no-default.quarkus.hibernate-orm.testA.datasource=testA +%multi-resource-no-default.quarkus.hibernate-orm.testA.database.generation=drop-and-create +%multi-resource-no-default.quarkus.datasource.testB.db-kind=h2 +%multi-resource-no-default.quarkus.hibernate-orm.testB.packages=${jpa.model.packages} +%multi-resource-no-default.quarkus.hibernate-orm.testB.datasource=testB +%multi-resource-no-default.quarkus.hibernate-orm.testB.database.generation=drop-and-create diff --git a/integration-tests/jpa/src/test/java/org/apache/camel/quarkus/component/jpa/it/JpaMultipleNamedResourcesWithNoDefaultAndExplicitEntityManagerFactoryTest.java b/integration-tests/jpa/src/test/java/org/apache/camel/quarkus/component/jpa/it/JpaMultipleNamedResourcesWithNoDefaultAndExplicitEntityManagerFactoryTest.java new file mode 100644 index 0000000000..4e801a4388 --- /dev/null +++ b/integration-tests/jpa/src/test/java/org/apache/camel/quarkus/component/jpa/it/JpaMultipleNamedResourcesWithNoDefaultAndExplicitEntityManagerFactoryTest.java @@ -0,0 +1,52 @@ +/* + * 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.quarkus.component.jpa.it; + +import java.util.Map; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import io.restassured.RestAssured; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.is; + +@QuarkusTest +@TestProfile(JpaMultipleNamedResourcesWithNoDefaultAndExplicitEntityManagerFactoryTest.class) +public class JpaMultipleNamedResourcesWithNoDefaultAndExplicitEntityManagerFactoryTest implements QuarkusTestProfile { + @Test + void multipleNamedResourcesWithoutDefaultAutowiresChosenEntityManagerFactory() { + RestAssured.given() + .get("/jpa/entityManagerFactory") + .then() + .statusCode(200) + .body( + "name", is("testB"), + "default", is(false)); + } + + @Override + public String getConfigProfile() { + return "multi-resource-no-default"; + } + + @Override + public Map<String, String> getConfigOverrides() { + return Map.of("camel.component.jpa.entity-managerFactory", "#testB"); + } +} diff --git a/integration-tests/jpa/src/test/java/org/apache/camel/quarkus/component/jpa/it/JpaMultipleNamedResourcesWithNoDefaultTest.java b/integration-tests/jpa/src/test/java/org/apache/camel/quarkus/component/jpa/it/JpaMultipleNamedResourcesWithNoDefaultTest.java new file mode 100644 index 0000000000..50d1766b72 --- /dev/null +++ b/integration-tests/jpa/src/test/java/org/apache/camel/quarkus/component/jpa/it/JpaMultipleNamedResourcesWithNoDefaultTest.java @@ -0,0 +1,46 @@ +/* + * 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.quarkus.component.jpa.it; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import io.restassured.RestAssured; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.endsWith; + +@QuarkusTest +@TestProfile(JpaMultipleNamedResourcesWithNoDefaultTest.class) +public class JpaMultipleNamedResourcesWithNoDefaultTest implements QuarkusTestProfile { + + @Test + void multipleNamedResourcesWithoutDefaultNotAutowirable() { + // There are multiple named EntityManagerFactory beans but no default bean + // EntityManagerFactory autowiring should fail + RestAssured.given() + .get("/jpa/entityManagerFactory") + .then() + .statusCode(500) + .body(endsWith("Check quarkus.hibernate-orm configuration.")); + } + + @Override + public String getConfigProfile() { + return "multi-resource-no-default"; + } +} diff --git a/integration-tests/jpa/src/test/java/org/apache/camel/quarkus/component/jpa/it/JpaSingleNamedResourceWithNoDefaultTest.java b/integration-tests/jpa/src/test/java/org/apache/camel/quarkus/component/jpa/it/JpaSingleNamedResourceWithNoDefaultTest.java new file mode 100644 index 0000000000..c1ee3d8d36 --- /dev/null +++ b/integration-tests/jpa/src/test/java/org/apache/camel/quarkus/component/jpa/it/JpaSingleNamedResourceWithNoDefaultTest.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.apache.camel.quarkus.component.jpa.it; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import io.restassured.RestAssured; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.is; + +@QuarkusTest +@TestProfile(JpaSingleNamedResourceWithNoDefaultTest.class) +public class JpaSingleNamedResourceWithNoDefaultTest implements QuarkusTestProfile { + @Test + void singleNamedResourceWithoutDefaultAutowiresEntityManagerFactory() { + RestAssured.given() + .get("/jpa/entityManagerFactory") + .then() + .statusCode(200) + .body( + "name", is("testA"), + "default", is(false)); + } + + @Override + public String getConfigProfile() { + return "single-resource-no-default"; + } +} diff --git a/integration-tests/sql/src/main/java/org/apache/camel/quarkus/component/sql/it/SqlDbInitializer.java b/integration-tests/sql/src/main/java/org/apache/camel/quarkus/component/sql/it/SqlDbInitializer.java index fa2b9c24ea..f651f1b6f7 100644 --- a/integration-tests/sql/src/main/java/org/apache/camel/quarkus/component/sql/it/SqlDbInitializer.java +++ b/integration-tests/sql/src/main/java/org/apache/camel/quarkus/component/sql/it/SqlDbInitializer.java @@ -25,6 +25,7 @@ import java.sql.SQLException; import java.sql.Statement; import io.agroal.api.AgroalDataSource; +import io.quarkus.arc.profile.UnlessBuildProfile; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -32,6 +33,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; @ApplicationScoped +@UnlessBuildProfile(anyOf = { "multi-ds-no-default", "multi-ds-with-default" }) public class SqlDbInitializer { private static final Logger LOGGER = LoggerFactory.getLogger(SqlDbInitializer.class); diff --git a/integration-tests/sql/src/main/java/org/apache/camel/quarkus/component/sql/it/SqlResource.java b/integration-tests/sql/src/main/java/org/apache/camel/quarkus/component/sql/it/SqlResource.java index 36849c2477..0c6af9c4f0 100644 --- a/integration-tests/sql/src/main/java/org/apache/camel/quarkus/component/sql/it/SqlResource.java +++ b/integration-tests/sql/src/main/java/org/apache/camel/quarkus/component/sql/it/SqlResource.java @@ -20,6 +20,7 @@ import java.net.URI; import java.util.*; import java.util.stream.Collectors; +import io.quarkus.arc.profile.UnlessBuildProfile; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.inject.Named; @@ -42,6 +43,7 @@ import org.springframework.util.LinkedCaseInsensitiveMap; @Path("/sql") @ApplicationScoped +@UnlessBuildProfile(anyOf = { "multi-ds-no-default", "multi-ds-with-default" }) public class SqlResource { @ConfigProperty(name = "quarkus.datasource.db-kind") diff --git a/integration-tests/sql/src/main/java/org/apache/camel/quarkus/component/sql/it/SqlRoutes.java b/integration-tests/sql/src/main/java/org/apache/camel/quarkus/component/sql/it/SqlRoutes.java index 07fa20034b..a1d2b25093 100644 --- a/integration-tests/sql/src/main/java/org/apache/camel/quarkus/component/sql/it/SqlRoutes.java +++ b/integration-tests/sql/src/main/java/org/apache/camel/quarkus/component/sql/it/SqlRoutes.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import io.agroal.api.AgroalDataSource; +import io.quarkus.arc.profile.UnlessBuildProfile; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; import jakarta.inject.Inject; @@ -40,6 +41,7 @@ import org.apache.camel.processor.idempotent.jdbc.JdbcMessageIdRepository; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.springframework.transaction.jta.JtaTransactionManager; +@UnlessBuildProfile(anyOf = { "multi-ds-no-default", "multi-ds-with-default" }) @ApplicationScoped public class SqlRoutes extends RouteBuilder { diff --git a/integration-tests/sql/src/main/java/org/apache/camel/quarkus/component/sql/it/datasource/SqlDataSourceResource.java b/integration-tests/sql/src/main/java/org/apache/camel/quarkus/component/sql/it/datasource/SqlDataSourceResource.java new file mode 100644 index 0000000000..b705c36c0f --- /dev/null +++ b/integration-tests/sql/src/main/java/org/apache/camel/quarkus/component/sql/it/datasource/SqlDataSourceResource.java @@ -0,0 +1,64 @@ +/* + * 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.quarkus.component.sql.it.datasource; + +import java.util.Map; + +import javax.sql.DataSource; + +import io.quarkus.arc.ClientProxy; +import io.quarkus.arc.InjectableBean; +import jakarta.enterprise.inject.Default; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.apache.camel.CamelContext; +import org.apache.camel.component.sql.SqlEndpoint; + +@Path("/sql/datasource") +public class SqlDataSourceResource { + @Inject + CamelContext camelContext; + + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response createSqlEndpoint(@QueryParam("dataSourceRef") String dataSourceRef) { + try { + String uri = "sql:SELECT * FROM some_table"; + if (dataSourceRef != null) { + uri += "?dataSource=#" + dataSourceRef; + } + + SqlEndpoint endpoint = camelContext.getEndpoint(uri, SqlEndpoint.class); + DataSource dataSource = endpoint.getDataSource(); + + InjectableBean<?> bean = ((ClientProxy) dataSource).arc_bean(); + String beanName = bean.getName() == null ? "" : bean.getName(); + Map<String, Object> response = Map.of( + "name", beanName, + "default", bean.getQualifiers().contains(Default.Literal.INSTANCE)); + + return Response.ok(response).build(); + } catch (Exception e) { + return Response.serverError().entity(e.getMessage()).build(); + } + } +} diff --git a/integration-tests/sql/src/main/resources/application.properties b/integration-tests/sql/src/main/resources/application.properties index 146ece032b..a2364e4000 100644 --- a/integration-tests/sql/src/main/resources/application.properties +++ b/integration-tests/sql/src/main/resources/application.properties @@ -15,7 +15,24 @@ ## limitations under the License. ## --------------------------------------------------------------------------- -quarkus.datasource.db-kind=${cq.sqlJdbcKind:h2} +# Default profile configuration +%dev,test,prod.quarkus.datasource.db-kind=${cq.sqlJdbcKind:h2} + +# multi-ds-with-default profile to test multiple named DataSource beans with the default DataSource +%multi-ds-with-default.quarkus.camel.routes-discovery.exclude-patterns=**/* +%multi-ds-with-default.quarkus.datasource.db-kind=h2 +%multi-ds-with-default.quarkus.datasource.devservices.enabled=false +%multi-ds-with-default.quarkus.datasource.testA.db-kind=h2 +%multi-ds-with-default.quarkus.datasource.testA.devservices.enabled=false +%multi-ds-with-default.quarkus.datasource.testB.db-kind=h2 +%multi-ds-with-default.quarkus.datasource.testB.devservices.enabled=false + +# multi-ds-no-default profile to test multiple named DataSource beans without a default DataSource +%multi-ds-no-default.quarkus.camel.routes-discovery.exclude-patterns=**/* +%multi-ds-no-default.quarkus.datasource.testA.db-kind=h2 +%multi-ds-no-default.quarkus.datasource.testA.devservices.enabled=false +%multi-ds-no-default.quarkus.datasource.testB.db-kind=h2 +%multi-ds-no-default.quarkus.datasource.testB.devservices.enabled=false # # Camel Quarkus SQL diff --git a/integration-tests/sql/src/test/java/org/apache/camel/quarkus/component/sql/it/SqlMultipleNamedDatasourceWithDefaultTest.java b/integration-tests/sql/src/test/java/org/apache/camel/quarkus/component/sql/it/SqlMultipleNamedDatasourceWithDefaultTest.java new file mode 100644 index 0000000000..929f9bd92e --- /dev/null +++ b/integration-tests/sql/src/test/java/org/apache/camel/quarkus/component/sql/it/SqlMultipleNamedDatasourceWithDefaultTest.java @@ -0,0 +1,58 @@ +/* + * 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.quarkus.component.sql.it; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import io.restassured.RestAssured; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.blankOrNullString; +import static org.hamcrest.Matchers.is; + +@QuarkusTest +@TestProfile(SqlMultipleNamedDatasourceWithDefaultTest.class) +public class SqlMultipleNamedDatasourceWithDefaultTest implements QuarkusTestProfile { + @Test + void multipleNamedDataSourcesWithDefaultAutowired() { + RestAssured.given() + .get("/sql/datasource") + .then() + .statusCode(200) + .body( + "name", blankOrNullString(), + "default", is(true)); + } + + @Test + void multipleNamedDataSourcesWithExplicitLookup() { + RestAssured.given() + .queryParam("dataSourceRef", "testB") + .get("/sql/datasource") + .then() + .statusCode(200) + .body( + "name", is("testB"), + "default", is(false)); + } + + @Override + public String getConfigProfile() { + return "multi-ds-with-default"; + } +} diff --git a/integration-tests/sql/src/test/java/org/apache/camel/quarkus/component/sql/it/SqlMultipleNamedDatasourceWithNoDefaultTest.java b/integration-tests/sql/src/test/java/org/apache/camel/quarkus/component/sql/it/SqlMultipleNamedDatasourceWithNoDefaultTest.java new file mode 100644 index 0000000000..0c82e38a98 --- /dev/null +++ b/integration-tests/sql/src/test/java/org/apache/camel/quarkus/component/sql/it/SqlMultipleNamedDatasourceWithNoDefaultTest.java @@ -0,0 +1,58 @@ +/* + * 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.quarkus.component.sql.it; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import io.restassured.RestAssured; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.is; + +@QuarkusTest +@TestProfile(SqlMultipleNamedDatasourceWithNoDefaultTest.class) +public class SqlMultipleNamedDatasourceWithNoDefaultTest implements QuarkusTestProfile { + @Test + void multipleNamedDataSourcesWithoutDefaultNotAutowirable() { + // There are multiple named DataSource beans but no default bean + // DataSource autowiring should fail + RestAssured.given() + .get("/sql/datasource") + .then() + .statusCode(500) + .body(endsWith("DataSource must be configured")); + } + + @Test + void multipleNamedDataSourcesWithExplicitLookup() { + RestAssured.given() + .queryParam("dataSourceRef", "testB") + .get("/sql/datasource") + .then() + .statusCode(200) + .body( + "name", is("testB"), + "default", is(false)); + } + + @Override + public String getConfigProfile() { + return "multi-ds-no-default"; + } +}