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"));
+    }
 }

Reply via email to