This is an automated email from the ASF dual-hosted git repository. jamesnetherton pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
The following commit(s) were added to refs/heads/main by this push: new a7a3e3a652 Avoid producing duplicate synthetic beans for @EndpointInject and @Produce a7a3e3a652 is described below commit a7a3e3a652e673902332bee8cbce3f4c2d7a2d71 Author: James Netherton <jamesnether...@gmail.com> AuthorDate: Fri May 2 08:18:39 2025 +0100 Avoid producing duplicate synthetic beans for @EndpointInject and @Produce Fixes #7299 --- .../core/deployment/InjectionPointsProcessor.java | 135 +++++++++------------ .../camel/quarkus/core/it/annotations/BeanA.java | 56 +++++++++ .../camel/quarkus/core/it/annotations/BeanB.java | 56 +++++++++ .../it/annotations/CoreAnnotationsResource.java | 27 +++++ .../core/it/annotations/CoreAnnotationsTest.java | 15 +++ 5 files changed, 210 insertions(+), 79 deletions(-) diff --git a/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/InjectionPointsProcessor.java b/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/InjectionPointsProcessor.java index 86a883a6f6..fd3332d72f 100644 --- a/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/InjectionPointsProcessor.java +++ b/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/InjectionPointsProcessor.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; @@ -34,11 +33,11 @@ import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.arc.processor.BuildExtension; import io.quarkus.arc.processor.InjectionPointInfo; import io.quarkus.arc.processor.QualifierRegistrar; +import io.quarkus.deployment.Capabilities; 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.CapabilityBuildItem; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem; import jakarta.inject.Inject; @@ -205,10 +204,9 @@ public class InjectionPointsProcessor { void qualifierRegistrars( BuildProducer<QualifierRegistrarBuildItem> qualifierRegistrars) { qualifierRegistrars.produce(new QualifierRegistrarBuildItem(new QualifierRegistrar() { - @Override public Map<DotName, Set<String>> getAdditionalQualifiers() { - Map<DotName, Set<String>> result = new LinkedHashMap<DotName, Set<String>>(); + Map<DotName, Set<String>> result = new LinkedHashMap<>(); result.put(ENDPOINT_INJECT_ANNOTATION, Collections.emptySet()); result.put(PRODUCE_ANNOTATION, Collections.emptySet()); return Collections.unmodifiableMap(result); @@ -221,19 +219,20 @@ public class InjectionPointsProcessor { void syntheticBeans( CamelRecorder recorder, CombinedIndexBuildItem index, - List<CapabilityBuildItem> capabilities, + Capabilities capabilities, BuildProducer<SyntheticBeanBuildItem> syntheticBeans, BuildProducer<NativeImageProxyDefinitionBuildItem> proxyDefinitions) { - Set<String> alreadyCreated = new HashSet<>(); + Set<String> injectionPointIdentifiers = new HashSet<>(); - for (AnnotationInstance annot : index.getIndex().getAnnotations(ENDPOINT_INJECT_ANNOTATION)) { - final AnnotationTarget target = annot.target(); + for (AnnotationInstance annotationInstance : index.getIndex().getAnnotations(ENDPOINT_INJECT_ANNOTATION)) { + final AnnotationTarget target = annotationInstance.target(); switch (target.kind()) { case FIELD: { - final FieldInfo field = target.asField(); - if (!excludeTestSyntheticBeanDuplicities(annot, alreadyCreated, field.declaringClass(), index.getIndex())) { - endpointInjectBeans(recorder, syntheticBeans, index.getIndex(), annot, field.type().name()); + // Avoid producing multiple beans for the same @EndpointInject URI + String identifier = annotationIdentifier(annotationInstance); + if (injectionPointIdentifiers.add(identifier)) { + endpointInjectBeans(recorder, syntheticBeans, index.getIndex(), annotationInstance, target.asField()); } break; } @@ -247,16 +246,15 @@ public class InjectionPointsProcessor { } } - AtomicReference<Boolean> beanCapabilityAvailable = new AtomicReference<>(); + for (AnnotationInstance annotation : index.getIndex().getAnnotations(PRODUCE_ANNOTATION)) { + final AnnotationTarget target = annotation.target(); + String identifier = annotationIdentifier(annotation); - for (AnnotationInstance annot : index.getIndex().getAnnotations(PRODUCE_ANNOTATION)) { - final AnnotationTarget target = annot.target(); switch (target.kind()) { case FIELD: { - final FieldInfo field = target.asField(); - if (!excludeTestSyntheticBeanDuplicities(annot, alreadyCreated, field.declaringClass(), index.getIndex())) { - produceBeans(recorder, capabilities, syntheticBeans, proxyDefinitions, beanCapabilityAvailable, - index.getIndex(), annot, field.type().name(), field.name(), field.declaringClass().name()); + if (injectionPointIdentifiers.add(identifier)) { + produceBeans(recorder, capabilities, syntheticBeans, proxyDefinitions, index.getIndex(), annotation, + target.asField()); } break; } @@ -271,58 +269,22 @@ public class InjectionPointsProcessor { } } - private boolean excludeTestSyntheticBeanDuplicities(AnnotationInstance annot, Set<String> alreadyCreated, - ClassInfo declaringClass, IndexView index) { - String identifier = annot.toString(false) + ":" + getTargetClass(annot).toString(); - - if (extendsCamelQuarkusTest(declaringClass, index)) { - if (alreadyCreated.contains(identifier)) { - return true; - } else { - alreadyCreated.add(identifier); - } - } - return false; - } - - private DotName getTargetClass(AnnotationInstance annot) { - switch (annot.target().kind()) { - case FIELD: - return annot.target().asField().type().name(); - case METHOD: - return annot.target().asMethod().returnType().name(); - default: - return null; - } - } - - private boolean extendsCamelQuarkusTest(ClassInfo declaringClass, IndexView indexView) { - if (declaringClass == null) { - return false; - } - - if (TEST_SUPPORT_CLASS_NAME.equals(declaringClass.name())) { - return true; - } - - //iterate over parent until found CamelQuarkusTest or null - return (declaringClass.superName() != null && - extendsCamelQuarkusTest(indexView.getClassByName(declaringClass.superName()), indexView)); - } - - void produceBeans(CamelRecorder recorder, List<CapabilityBuildItem> capabilities, + void produceBeans( + CamelRecorder recorder, + Capabilities capabilities, BuildProducer<SyntheticBeanBuildItem> syntheticBeans, BuildProducer<NativeImageProxyDefinitionBuildItem> proxyDefinitions, - AtomicReference<Boolean> beanCapabilityAvailable, IndexView index, - AnnotationInstance annot, final DotName fieldType, String annotationTarget, DotName declaringClass) { + AnnotationInstance annot, + FieldInfo field) { try { + Type fieldType = field.type(); Class<?> clazz = Class.forName(fieldType.toString(), false, Thread.currentThread().getContextClassLoader()); if (ProducerTemplate.class.isAssignableFrom(clazz)) { syntheticBeans.produce( SyntheticBeanBuildItem - .configure(fieldType) + .configure(fieldType.name()) .setRuntimeInit().scope(Singleton.class) .supplier( recorder.createProducerTemplate(resolveAnnotValue(index, annot))) @@ -335,7 +297,7 @@ public class InjectionPointsProcessor { } else if (FluentProducerTemplate.class.isAssignableFrom(clazz)) { syntheticBeans.produce( SyntheticBeanBuildItem - .configure(fieldType) + .configure(fieldType.name()) .setRuntimeInit().scope(Singleton.class) .supplier( recorder.createFluentProducerTemplate(resolveAnnotValue(index, annot))) @@ -347,24 +309,18 @@ public class InjectionPointsProcessor { */ } else if (clazz.isInterface()) { /* Requires camel-quarkus-bean */ - - if (beanCapabilityAvailable.get() == null) { - beanCapabilityAvailable.set(capabilities.stream() - .map(CapabilityBuildItem::getName) - .anyMatch(CamelCapabilities.BEAN::equals)); - } - if (!beanCapabilityAvailable.get()) { + if (capabilities.isMissing(CamelCapabilities.BEAN)) { throw new IllegalStateException( "Add camel-quarkus-bean dependency to be able to use @org.apache.camel.Produce on fields with interface type: " - + fieldType.toString() - + " " + annotationTarget + " in " - + declaringClass.toString()); + + fieldType.name() + + " " + field.name() + " in " + + field.declaringClass().name()); } - proxyDefinitions.produce(new NativeImageProxyDefinitionBuildItem(fieldType.toString())); + proxyDefinitions.produce(new NativeImageProxyDefinitionBuildItem(fieldType.name().toString())); syntheticBeans.produce( SyntheticBeanBuildItem - .configure(fieldType) + .configure(fieldType.name()) .setRuntimeInit().scope(Singleton.class) .supplier( recorder.produceProxy(clazz, resolveAnnotValue(index, annot))) @@ -377,14 +333,19 @@ public class InjectionPointsProcessor { } @SuppressWarnings("unchecked") - private void endpointInjectBeans(CamelRecorder recorder, BuildProducer<SyntheticBeanBuildItem> syntheticBeans, - IndexView index, AnnotationInstance annot, final DotName fieldType) { + private void endpointInjectBeans( + CamelRecorder recorder, + BuildProducer<SyntheticBeanBuildItem> syntheticBeans, + IndexView index, + AnnotationInstance annot, + FieldInfo field) { try { + Type fieldType = field.type(); Class<?> clazz = Class.forName(fieldType.toString()); if (Endpoint.class.isAssignableFrom(clazz)) { syntheticBeans.produce( SyntheticBeanBuildItem - .configure(fieldType) + .configure(fieldType.name()) .setRuntimeInit().scope(Singleton.class) .supplier( recorder.createEndpoint(resolveAnnotValue(index, annot), @@ -394,7 +355,7 @@ public class InjectionPointsProcessor { } else if (ProducerTemplate.class.isAssignableFrom(clazz)) { syntheticBeans.produce( SyntheticBeanBuildItem - .configure(fieldType) + .configure(fieldType.name()) .setRuntimeInit().scope(Singleton.class) .supplier( recorder.createProducerTemplate(resolveAnnotValue(index, annot))) @@ -407,7 +368,7 @@ public class InjectionPointsProcessor { } else if (FluentProducerTemplate.class.isAssignableFrom(clazz)) { syntheticBeans.produce( SyntheticBeanBuildItem - .configure(fieldType) + .configure(fieldType.name()) .setRuntimeInit().scope(Singleton.class) .supplier( recorder.createFluentProducerTemplate(resolveAnnotValue(index, annot))) @@ -426,4 +387,20 @@ public class InjectionPointsProcessor { private String resolveAnnotValue(IndexView index, AnnotationInstance annot) { return annot.valueWithDefault(index).asString(); } + + private String annotationIdentifier(AnnotationInstance annotationInstance) { + return annotationInstance.toString(false) + ":" + getTargetClass(annotationInstance); + } + + private DotName getTargetClass(AnnotationInstance annotationInstance) { + AnnotationTarget target = annotationInstance.target(); + switch (target.kind()) { + case FIELD: + return target.asField().type().name(); + case METHOD: + return target.asMethod().returnType().name(); + default: + return null; + } + } } diff --git a/integration-test-groups/foundation/core-annotations/src/main/java/org/apache/camel/quarkus/core/it/annotations/BeanA.java b/integration-test-groups/foundation/core-annotations/src/main/java/org/apache/camel/quarkus/core/it/annotations/BeanA.java new file mode 100644 index 0000000000..2268ee758d --- /dev/null +++ b/integration-test-groups/foundation/core-annotations/src/main/java/org/apache/camel/quarkus/core/it/annotations/BeanA.java @@ -0,0 +1,56 @@ +/* + * 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.it.annotations; + +import io.quarkus.arc.ClientProxy; +import jakarta.enterprise.context.ApplicationScoped; +import org.apache.camel.EndpointInject; +import org.apache.camel.FluentProducerTemplate; +import org.apache.camel.Produce; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.component.direct.DirectEndpoint; + +@ApplicationScoped +public class BeanA { + @EndpointInject("direct:endpointInjectDirect1") + DirectEndpoint endpointInjectDirect1; + + @EndpointInject("direct:endpointInjectDirect2") + DirectEndpoint endpointInjectDirect2; + + @Produce("direct:produceProducer") + ProducerTemplate produceProducer; + + @Produce("direct:produceProducerFluent") + FluentProducerTemplate produceProducerFluent; + + public DirectEndpoint getEndpointInjectDirect1() { + return ClientProxy.unwrap(endpointInjectDirect1); + } + + public DirectEndpoint getEndpointInjectDirect2() { + return ClientProxy.unwrap(endpointInjectDirect2); + } + + public ProducerTemplate getProduceProducer() { + return ClientProxy.unwrap(produceProducer); + } + + public FluentProducerTemplate getProduceProducerFluent() { + return ClientProxy.unwrap(produceProducerFluent); + } +} diff --git a/integration-test-groups/foundation/core-annotations/src/main/java/org/apache/camel/quarkus/core/it/annotations/BeanB.java b/integration-test-groups/foundation/core-annotations/src/main/java/org/apache/camel/quarkus/core/it/annotations/BeanB.java new file mode 100644 index 0000000000..8004d7ca84 --- /dev/null +++ b/integration-test-groups/foundation/core-annotations/src/main/java/org/apache/camel/quarkus/core/it/annotations/BeanB.java @@ -0,0 +1,56 @@ +/* + * 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.it.annotations; + +import io.quarkus.arc.ClientProxy; +import jakarta.enterprise.context.ApplicationScoped; +import org.apache.camel.EndpointInject; +import org.apache.camel.FluentProducerTemplate; +import org.apache.camel.Produce; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.component.direct.DirectEndpoint; + +@ApplicationScoped +public class BeanB { + @EndpointInject("direct:endpointInjectDirect1") + DirectEndpoint endpointInjectDirect1; + + @EndpointInject("direct:endpointInjectDirect2") + DirectEndpoint endpointInjectDirect2; + + @Produce("direct:produceProducer") + ProducerTemplate produceProducer; + + @Produce("direct:produceProducerFluent") + FluentProducerTemplate produceProducerFluent; + + public DirectEndpoint getEndpointInjectDirect1() { + return ClientProxy.unwrap(endpointInjectDirect1); + } + + public DirectEndpoint getEndpointInjectDirect2() { + return ClientProxy.unwrap(endpointInjectDirect2); + } + + public ProducerTemplate getProduceProducer() { + return ClientProxy.unwrap(produceProducer); + } + + public FluentProducerTemplate getProduceProducerFluent() { + return ClientProxy.unwrap(produceProducerFluent); + } +} diff --git a/integration-test-groups/foundation/core-annotations/src/main/java/org/apache/camel/quarkus/core/it/annotations/CoreAnnotationsResource.java b/integration-test-groups/foundation/core-annotations/src/main/java/org/apache/camel/quarkus/core/it/annotations/CoreAnnotationsResource.java index 537fc4f796..9a163038a5 100644 --- a/integration-test-groups/foundation/core-annotations/src/main/java/org/apache/camel/quarkus/core/it/annotations/CoreAnnotationsResource.java +++ b/integration-test-groups/foundation/core-annotations/src/main/java/org/apache/camel/quarkus/core/it/annotations/CoreAnnotationsResource.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import io.quarkus.arc.ClientProxy; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.inject.Named; @@ -63,6 +64,12 @@ public class CoreAnnotationsResource { @Named("results") Map<String, List<String>> results; + @Inject + BeanA beanA; + + @Inject + BeanB beanB; + @Path("/routes/lookup-routes") @GET @Produces(MediaType.TEXT_PLAIN) @@ -120,6 +127,26 @@ public class CoreAnnotationsResource { return awaitFirst("produceProducerFluent"); } + @GET + @Path("/endpointInject/sameInstance") + @Produces(MediaType.TEXT_PLAIN) + public boolean isEndpointInjectSameInstance() { + BeanA unwrapedBeanA = ClientProxy.unwrap(beanA); + BeanB unwrapedBeanB = ClientProxy.unwrap(beanB); + return unwrapedBeanA.getEndpointInjectDirect1() == unwrapedBeanB.getEndpointInjectDirect1() && + unwrapedBeanA.getEndpointInjectDirect2() == unwrapedBeanB.getEndpointInjectDirect2(); + } + + @GET + @Path("/produceInject/sameInstance") + @Produces(MediaType.TEXT_PLAIN) + public boolean isProduceInjectSameInstance() { + BeanA unwrapedBeanA = ClientProxy.unwrap(beanA); + BeanB unwrapedBeanB = ClientProxy.unwrap(beanB); + return unwrapedBeanA.getProduceProducer() == unwrapedBeanB.getProduceProducer() && + unwrapedBeanA.getProduceProducerFluent() == unwrapedBeanB.getProduceProducerFluent(); + } + String awaitFirst(String key) { final List<String> list = results.get(key); final long timeout = System.currentTimeMillis() + 10000; diff --git a/integration-test-groups/foundation/core-annotations/src/test/java/org/apache/camel/quarkus/core/it/annotations/CoreAnnotationsTest.java b/integration-test-groups/foundation/core-annotations/src/test/java/org/apache/camel/quarkus/core/it/annotations/CoreAnnotationsTest.java index 2a74af79c9..90f18f2c27 100644 --- a/integration-test-groups/foundation/core-annotations/src/test/java/org/apache/camel/quarkus/core/it/annotations/CoreAnnotationsTest.java +++ b/integration-test-groups/foundation/core-annotations/src/test/java/org/apache/camel/quarkus/core/it/annotations/CoreAnnotationsTest.java @@ -91,4 +91,19 @@ public class CoreAnnotationsTest { .body(equalTo("Sent to an @Produce: abc")); } + @Test + public void endpointInjectSameInstance() { + RestAssured.given() + .get("/core/annotations/endpointInject/sameInstance") + .then() + .body(equalTo("true")); + } + + @Test + public void produceInjectSameInstance() { + RestAssured.given() + .get("/core/annotations/produceInject/sameInstance") + .then() + .body(equalTo("true")); + } }