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 <[email protected]>
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"));
+ }
}