This is an automated email from the ASF dual-hosted git repository. lburgazzoli pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
The following commit(s) were added to refs/heads/master by this push: new f8862de Type safe component injection f8862de is described below commit f8862de6e48ee41eddc5d3dec84d72b61e2f6895 Author: lburgazzoli <lburgazz...@gmail.com> AuthorDate: Thu Jun 18 12:17:06 2020 +0200 Type safe component injection This allows to: ```java @ApplicationScoped public static class Configurer { @Inject LogComponent log; @PostConstruct void setUpLogComponent() { log.setBasicPropertyBinding(true); log.setExchangeFormatter(new MyExchangeFormatter()); } public LogComponent getLog() { return log; } } @ApplicationScoped public static class Holder { private LogComponent log; @Inject public Holder(LogComponent log) { this.log = log; } public LogComponent getLog() { return log; } } ``` --- .../core/deployment/InjectionPointsProcessor.java | 124 +++++++++++++++++++++ .../core/runtime/CamelInjectionPointTest.java | 91 +++++++++++++++ .../quarkus/core/InjectionPointsRecorder.java | 47 ++++++++ 3 files changed, 262 insertions(+) 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 new file mode 100644 index 0000000..79f7b20 --- /dev/null +++ b/extensions-core/core/deployment/src/main/java/org/apache/camel/quarkus/core/deployment/InjectionPointsProcessor.java @@ -0,0 +1,124 @@ +/* + * 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.deployment; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Supplier; + +import javax.inject.Named; +import javax.inject.Singleton; + +import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem; +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.arc.processor.BuildExtension; +import io.quarkus.arc.processor.InjectionPointInfo; +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.CombinedIndexBuildItem; +import org.apache.camel.Component; +import org.apache.camel.quarkus.core.InjectionPointsRecorder; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.jboss.logging.Logger; + +public class InjectionPointsProcessor { + private static final Logger LOGGER = Logger.getLogger(InjectionPointsProcessor.class); + + private static final DotName ANNOTATION_NAME_NAMED = DotName.createSimple( + Named.class.getName()); + private static final DotName INTERFACE_NAME_COMPONENT = DotName.createSimple( + Component.class.getName()); + + private static SyntheticBeanBuildItem syntheticBean(DotName name, Supplier<?> creator) { + return SyntheticBeanBuildItem.configure(name) + .supplier(creator) + .scope(Singleton.class) + .unremovable() + .setRuntimeInit() + .done(); + } + + @Record(ExecutionTime.RUNTIME_INIT) + @BuildStep + BeanRegistrationPhaseBuildItem.BeanConfiguratorBuildItem injectedComponents( + CombinedIndexBuildItem index, + InjectionPointsRecorder recorder, + BeanRegistrationPhaseBuildItem beanRegistrationPhase, + BuildProducer<SyntheticBeanBuildItem> syntheticBeans) { + + final Collection<ClassInfo> components = index.getIndex().getAllKnownImplementors(INTERFACE_NAME_COMPONENT); + final Set<String> created = new HashSet<>(); + + for (InjectionPointInfo injectionPoint : beanRegistrationPhase.getContext().get(BuildExtension.Key.INJECTION_POINTS)) { + if (injectionPoint.getTarget().kind() == AnnotationTarget.Kind.FIELD) { + FieldInfo target = injectionPoint.getTarget().asField(); + + if (!created.add(target.type().name().toString())) { + continue; + } + + if (components.stream().anyMatch(ci -> Objects.equals(ci.name(), target.type().name()))) { + final AnnotationInstance named = target.annotation(ANNOTATION_NAME_NAMED); + final String componentName = named == null ? target.name() : named.value().asString(); + final Supplier<?> creator = recorder.componentSupplier(componentName, target.type().toString()); + + LOGGER.debugf("Creating synthetic component bean [name=%s, type=%s]", componentName, target.type().name()); + + syntheticBeans.produce(syntheticBean(target.type().name(), creator)); + } + } + + if (injectionPoint.getTarget().kind() == AnnotationTarget.Kind.METHOD) { + final MethodInfo target = injectionPoint.getTarget().asMethod(); + final List<Type> types = target.parameters(); + + for (int i = 0; i < types.size(); i++) { + Type type = types.get(0); + + if (!created.add(type.name().toString())) { + continue; + } + + if (components.stream().anyMatch(ci -> Objects.equals(ci.name(), type.name()))) { + final AnnotationInstance named = target.annotation(ANNOTATION_NAME_NAMED); + final String componentName = named == null ? target.parameterName(i) : named.value().asString(); + final Supplier<?> creator = recorder.componentSupplier(componentName, type.toString()); + + LOGGER.debugf("Creating synthetic component bean [name=%s, type=%s]", componentName, type.name()); + + syntheticBeans.produce(syntheticBean(type.name(), creator)); + } + } + } + } + + // method using BeanRegistrationPhaseBuildItem should return a BeanConfiguratorBuildItem + // otherwise the build step may be processed at the wrong time. + return new BeanRegistrationPhaseBuildItem.BeanConfiguratorBuildItem(); + } +} diff --git a/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/runtime/CamelInjectionPointTest.java b/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/runtime/CamelInjectionPointTest.java new file mode 100644 index 0000000..597c24f --- /dev/null +++ b/extensions-core/core/deployment/src/test/java/org/apache/camel/quarkus/core/runtime/CamelInjectionPointTest.java @@ -0,0 +1,91 @@ +/* + * 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 javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import io.quarkus.test.QuarkusUnitTest; +import org.apache.camel.Exchange; +import org.apache.camel.component.log.LogComponent; +import org.apache.camel.spi.ExchangeFormatter; +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.assertj.core.api.Assertions.assertThat; + +public class CamelInjectionPointTest { + @RegisterExtension + static final QuarkusUnitTest CONFIG = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Configurer.class, MyExchangeFormatter.class)); + + @Inject + Configurer configurer; + @Inject + Holder holder; + + @Test + public void testConfigurer() { + assertThat(configurer.getLog()).isNotNull(); + assertThat(configurer.getLog().isBasicPropertyBinding()).isTrue(); + assertThat(configurer.getLog().getExchangeFormatter()).isInstanceOf(MyExchangeFormatter.class); + assertThat(holder.getLog()).isNotNull(); + assertThat(holder.getLog().isBasicPropertyBinding()).isTrue(); + assertThat(holder.getLog().getExchangeFormatter()).isInstanceOf(MyExchangeFormatter.class); + } + + @ApplicationScoped + public static class Configurer { + @Inject + LogComponent log; + + @PostConstruct + void setUpLogComponent() { + log.setBasicPropertyBinding(true); + log.setExchangeFormatter(new MyExchangeFormatter()); + } + + public LogComponent getLog() { + return log; + } + } + + @ApplicationScoped + public static class Holder { + private LogComponent log; + + @Inject + public Holder(LogComponent log) { + this.log = log; + } + + public LogComponent getLog() { + return log; + } + } + + public static class MyExchangeFormatter implements ExchangeFormatter { + @Override + public String format(Exchange exchange) { + return exchange.toString(); + } + } +} diff --git a/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/InjectionPointsRecorder.java b/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/InjectionPointsRecorder.java new file mode 100644 index 0000000..c281fad --- /dev/null +++ b/extensions-core/core/runtime/src/main/java/org/apache/camel/quarkus/core/InjectionPointsRecorder.java @@ -0,0 +1,47 @@ +/* + * 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; + +import java.util.function.Supplier; + +import io.quarkus.arc.Arc; +import io.quarkus.runtime.annotations.Recorder; +import org.apache.camel.CamelContext; +import org.apache.camel.Component; + +@Recorder +public class InjectionPointsRecorder { + public Supplier<? extends Component> componentSupplier(String componentName, String componentType) { + return new Supplier<Component>() { + @Override + public Component get() { + // We can't inject the CamelContext from the BuildStep as it will create a + // dependency cycle as the BuildStep that creates the CamelContext requires + // BeanManger instance. As this is a fairly trivial job, we can safely keep + // the context lookup-at runtime. + final CamelContext camelContext = Arc.container().instance(CamelContext.class).get(); + if (camelContext == null) { + throw new IllegalStateException("No CamelContext found"); + } + + return camelContext.getComponent( + componentName, + camelContext.getClassResolver().resolveClass(componentType, Component.class)); + } + }; + } +}