This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new 68ea079 Camel 17571 (#7200) 68ea079 is described below commit 68ea0794002beadbc63482455039004286ef2b7d Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Mon Mar 14 15:51:17 2022 +0100 Camel 17571 (#7200) * CAMEL-17571: camel-dsl - Allow to register custom annotation processors that can do custom logic after a DSL has compiled source into Java object. * CAMEL-17571: camel-jbang - Support for spring @Component/@Service annotations in custom beans * CAMEL-17571: camel-dsl - Allow to register custom annotation processors that can do custom logic after a DSL has compiled source into Java object. * CAMEL-17571: camel-dsl - Allow to register custom annotation processors that can do custom logic after a DSL has compiled source into Java object. * CAMEL-17571: camel-jbang - Support for spring @Autowired/@Value annotations in custom beans * CAMEL-17571: camel-jbang - Support for spring @Autowired/@Value annotations in custom beans * CAMEL-17571: camel-jbang - Support for spring @Bean annotations in custom beans * CAMEL-17571: camel-jbang - Support for spring @Bean annotations in custom beans * CAMEL-17571: camel-dsl - Allow to register custom annotation processors that can do custom logic after a DSL has compiled source into Java object. * CAMEL-17571: camel-dsl - Allow to register custom annotation processors that can do custom logic after a DSL has compiled source into Java object. * camel-kamelet-main - Cleanup code * CAMEL-17571: camel-jbang - Support for quarkus/cdi annotations in custom beans * CAMEL-17571: camel-jbang - Support for quarkus/cdi annotations in custom beans * CAMEL-17571: camel-jbang - Support for quarkus/cdi annotations in custom beans * CAMEL-17571: camel-jbang - docs * CAMEL-17571: camel-dsl - Allow to register custom annotation processors that can do custom logic after a DSL has compiled source into Java object. * CAMEL-17571: camel-dsl - Allow to register custom annotation processors that can do custom logic after a DSL has compiled source into Java object. * CAMEL-17571: camel-dsl - Allow to register custom annotation processors that can do custom logic after a DSL has compiled source into Java object. * CAMEL-17571: Polished * CAMEL-17571: Review * Update docs/user-manual/modules/ROOT/pages/camel-jbang.adoc Co-authored-by: Nicolas Filotto <essob...@users.noreply.github.com> Co-authored-by: Nicolas Filotto <essob...@users.noreply.github.com> --- camel-dependencies/pom.xml | 1 + .../apache/camel/spi/CamelBeanPostProcessor.java | 9 + .../camel/spi/CamelBeanPostProcessorInjector.java | 46 ++++ .../impl/engine/CamelPostProcessorHelper.java | 90 ++++++- .../impl/engine/DefaultCamelBeanPostProcessor.java | 102 ++------ .../impl/CamelBeanPostProcessorInjectorTest.java | 101 ++++++++ .../modules/ROOT/pages/camel-jbang.adoc | 34 +++ .../camel/dsl/support/CompilePostProcessor.java | 44 ++++ .../dsl/support/RouteBuilderLoaderSupport.java | 36 ++- .../dsl/java/joor/JavaRoutesBuilderLoader.java | 57 +--- dsl/camel-kamelet-main/pom.xml | 32 +++ .../camel/main/AnnotationDependencyInjection.java | 286 +++++++++++++++++++++ .../camel/main/DependencyDownloaderKamelet.java | 111 +++++++- .../java/org/apache/camel/main/KameletMain.java | 3 + .../org/apache/camel/main/KameletYamlRoutes.java | 130 ---------- parent/pom.xml | 1 + 16 files changed, 819 insertions(+), 264 deletions(-) diff --git a/camel-dependencies/pom.xml b/camel-dependencies/pom.xml index b676108..fcb98e3 100644 --- a/camel-dependencies/pom.xml +++ b/camel-dependencies/pom.xml @@ -287,6 +287,7 @@ <jackson2-version>2.13.2</jackson2-version> <jain-sip-ri-bundle-version>1.2.154_2</jain-sip-ri-bundle-version> <jakarta-api-version>2.1.5</jakarta-api-version> + <jakarta-cdi-api-version>2.0.2</jakarta-cdi-api-version> <jakarta-jaxb-version>2.3.3</jakarta-jaxb-version> <jakarta-mail-version>1.6.6</jakarta-mail-version> <jakarta.el-version>3.0.3</jakarta.el-version> diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/CamelBeanPostProcessor.java b/core/camel-api/src/main/java/org/apache/camel/spi/CamelBeanPostProcessor.java index 0308f54..1fff611 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/CamelBeanPostProcessor.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/CamelBeanPostProcessor.java @@ -83,4 +83,13 @@ public interface CamelBeanPostProcessor { return false; } + /** + * Adds a custom bean post injector + * + * @param injector the custom injector + */ + default void addCamelBeanPostProjectInjector(CamelBeanPostProcessorInjector injector) { + // noop + } + } diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/CamelBeanPostProcessorInjector.java b/core/camel-api/src/main/java/org/apache/camel/spi/CamelBeanPostProcessorInjector.java new file mode 100644 index 0000000..6c70ab2 --- /dev/null +++ b/core/camel-api/src/main/java/org/apache/camel/spi/CamelBeanPostProcessorInjector.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.spi; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * Used for custom injection when doing {@link CamelBeanPostProcessor} bean post-processing. Can be used to support + * 3rd-party annotations for dependency injections. + */ +public interface CamelBeanPostProcessorInjector { + + /** + * Field injection + * + * @param field the field + * @param bean the bean instance where the field is present + * @param beanName optional bean id of the bean + */ + void onFieldInject(Field field, Object bean, String beanName); + + /** + * Method injection + * + * @param method the method + * @param bean the bean instance where the method is present + * @param beanName optional bean id of the bean + */ + void onMethodInject(Method method, Object bean, String beanName); + +} diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelPostProcessorHelper.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelPostProcessorHelper.java index 04b4f95..533fb49 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelPostProcessorHelper.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/CamelPostProcessorHelper.java @@ -16,6 +16,7 @@ */ package org.apache.camel.impl.engine; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.LinkedHashMap; import java.util.Locale; @@ -23,6 +24,8 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import org.apache.camel.BeanConfigInject; +import org.apache.camel.BeanInject; import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; import org.apache.camel.Consume; @@ -38,12 +41,15 @@ import org.apache.camel.NoSuchBeanException; import org.apache.camel.PollingConsumer; import org.apache.camel.Producer; import org.apache.camel.ProducerTemplate; +import org.apache.camel.PropertyInject; import org.apache.camel.ProxyInstantiationException; import org.apache.camel.RuntimeCamelException; import org.apache.camel.Service; +import org.apache.camel.TypeConverter; import org.apache.camel.spi.BeanProxyFactory; import org.apache.camel.spi.PropertiesComponent; import org.apache.camel.spi.PropertyConfigurer; +import org.apache.camel.spi.Registry; import org.apache.camel.support.CamelContextHelper; import org.apache.camel.support.PropertyBindingSupport; import org.apache.camel.support.service.ServiceHelper; @@ -51,8 +57,10 @@ import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.camel.support.ObjectHelper.invokeMethod; + /** - * A helper class for Camel based injector or bean post processing hooks. + * A helper class for Camel based injector or bean post-processing hooks. */ public class CamelPostProcessorHelper implements CamelContextAware { @@ -402,6 +410,86 @@ public class CamelPostProcessorHelper implements CamelContextAware { return bean; } + public Object getInjectionBeanMethodValue( + CamelContext context, + Method method, Object bean, String beanName) { + Class<?> returnType = method.getReturnType(); + if (returnType == Void.TYPE) { + throw new IllegalArgumentException( + "@BindToRegistry on class: " + method.getDeclaringClass() + + " method: " + method.getName() + " with void return type is not allowed"); + } + + Object value; + Object[] parameters = bindToRegistryParameterMapping(context, method); + if (parameters != null) { + value = invokeMethod(method, bean, parameters); + } else { + value = invokeMethod(method, bean); + } + return value; + } + + private Object[] bindToRegistryParameterMapping(CamelContext context, Method method) { + if (method.getParameterCount() == 0) { + return null; + } + + // map each parameter if possible + Object[] parameters = new Object[method.getParameterCount()]; + for (int i = 0; i < method.getParameterCount(); i++) { + Class<?> type = method.getParameterTypes()[i]; + if (type.isAssignableFrom(CamelContext.class)) { + parameters[i] = context; + } else if (type.isAssignableFrom(Registry.class)) { + parameters[i] = context.getRegistry(); + } else if (type.isAssignableFrom(TypeConverter.class)) { + parameters[i] = context.getTypeConverter(); + } else { + // we also support @BeanInject and @PropertyInject annotations + Annotation[] anns = method.getParameterAnnotations()[i]; + if (anns.length == 1) { + // we dont assume there are multiple annotations on the same parameter so grab first + Annotation ann = anns[0]; + if (ann.annotationType() == PropertyInject.class) { + PropertyInject pi = (PropertyInject) ann; + Object result = getInjectionPropertyValue(type, pi.value(), pi.defaultValue(), + null, null, null); + parameters[i] = result; + } else if (ann.annotationType() == BeanConfigInject.class) { + BeanConfigInject pi = (BeanConfigInject) ann; + Object result = getInjectionBeanConfigValue(type, pi.value()); + parameters[i] = result; + } else if (ann.annotationType() == BeanInject.class) { + BeanInject bi = (BeanInject) ann; + Object result = getInjectionBeanValue(type, bi.value()); + parameters[i] = result; + } + } else { + // okay attempt to default to singleton instances from the registry + Set<?> instances = context.getRegistry().findByType(type); + if (instances.size() == 1) { + parameters[i] = instances.iterator().next(); + } else if (instances.size() > 1) { + // there are multiple instances of the same type, so barf + throw new IllegalArgumentException( + "Multiple beans of the same type: " + type + + " exists in the Camel registry. Specify the bean name on @BeanInject to bind to a single bean, at the method: " + + method); + } + } + } + + // each parameter must be mapped + if (parameters[i] == null) { + int pos = i + 1; + throw new IllegalArgumentException("@BindToProperty cannot bind parameter #" + pos + " on method: " + method); + } + } + + return parameters; + } + /** * Factory method to create a {@link org.apache.camel.ProducerTemplate} to be injected into a POJO */ diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java index 3a74646..6293b86 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultCamelBeanPostProcessor.java @@ -16,13 +16,11 @@ */ package org.apache.camel.impl.engine; -import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Set; import java.util.function.Function; import org.apache.camel.BeanConfigInject; @@ -35,9 +33,8 @@ import org.apache.camel.EndpointInject; import org.apache.camel.ExtendedCamelContext; import org.apache.camel.Produce; import org.apache.camel.PropertyInject; -import org.apache.camel.TypeConverter; import org.apache.camel.spi.CamelBeanPostProcessor; -import org.apache.camel.spi.Registry; +import org.apache.camel.spi.CamelBeanPostProcessorInjector; import org.apache.camel.support.DefaultEndpoint; import org.apache.camel.util.ReflectionHelper; import org.slf4j.Logger; @@ -65,6 +62,7 @@ import static org.apache.camel.util.ObjectHelper.isEmpty; public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, CamelContextAware { protected static final Logger LOG = LoggerFactory.getLogger(DefaultCamelBeanPostProcessor.class); + protected final List<CamelBeanPostProcessorInjector> beanPostProcessorInjectors = new ArrayList<>(); protected CamelPostProcessorHelper camelPostProcessorHelper; protected CamelContext camelContext; protected boolean enabled = true; @@ -106,6 +104,11 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca } @Override + public void addCamelBeanPostProjectInjector(CamelBeanPostProcessorInjector injector) { + this.beanPostProcessorInjectors.add(injector); + } + + @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws Exception { LOG.trace("Camel bean processing before initialization for bean: {}", beanName); @@ -268,6 +271,11 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca String uri = produce.value().isEmpty() ? produce.uri() : produce.value(); injectField(field, uri, produce.property(), bean, beanName, produce.binding()); } + + // custom bean injector on the field + for (CamelBeanPostProcessorInjector injector : beanPostProcessorInjectors) { + injector.onFieldInject(field, bean, beanName); + } }); } @@ -323,6 +331,11 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca setterInjection(method, bean, beanName); getPostProcessorHelper().consumerInjection(method, bean, beanName); + + // custom bean injector on the method + for (CamelBeanPostProcessorInjector injector : beanPostProcessorInjectors) { + injector.onMethodInject(method, bean, beanName); + } }); } @@ -498,12 +511,11 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca } Object value = ReflectionHelper.getField(field, bean); - // use dependency injection factory to perform the task of binding the bean to registry if (value != null) { - if (unbindEnabled) { getOrLookupCamelContext().getRegistry().unbind(name); } + // use dependency injection factory to perform the task of binding the bean to registry Runnable task = getOrLookupCamelContext().adapt(ExtendedCamelContext.class) .getDependencyInjectionAnnotationFactory() .createBindToRegistryFactory(name, value, beanName, beanPostProcess); @@ -515,26 +527,14 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca if (isEmpty(name)) { name = method.getName(); } - Class<?> returnType = method.getReturnType(); - if (returnType == null || returnType == Void.TYPE) { - throw new IllegalArgumentException( - "@BindToRegistry on class: " + method.getDeclaringClass() - + " method: " + method.getName() + " with void return type is not allowed"); - } + Object value = getPostProcessorHelper() + .getInjectionBeanMethodValue(getOrLookupCamelContext(), method, bean, beanName); - Object value; - Object[] parameters = bindToRegistryParameterMapping(method); - if (parameters != null) { - value = invokeMethod(method, bean, parameters); - } else { - value = invokeMethod(method, bean); - } - // use dependency injection factory to perform the task of binding the bean to registry if (value != null) { - if (unbindEnabled) { getOrLookupCamelContext().getRegistry().unbind(name); } + // use dependency injection factory to perform the task of binding the bean to registry Runnable task = getOrLookupCamelContext().adapt(ExtendedCamelContext.class) .getDependencyInjectionAnnotationFactory() .createBindToRegistryFactory(name, value, beanName, beanPostProcess); @@ -542,66 +542,6 @@ public class DefaultCamelBeanPostProcessor implements CamelBeanPostProcessor, Ca } } - private Object[] bindToRegistryParameterMapping(Method method) { - if (method.getParameterCount() == 0) { - return null; - } - - // map each parameter if possible - Object[] parameters = new Object[method.getParameterCount()]; - for (int i = 0; i < method.getParameterCount(); i++) { - Class<?> type = method.getParameterTypes()[i]; - if (type.isAssignableFrom(CamelContext.class)) { - parameters[i] = getOrLookupCamelContext(); - } else if (type.isAssignableFrom(Registry.class)) { - parameters[i] = getOrLookupCamelContext().getRegistry(); - } else if (type.isAssignableFrom(TypeConverter.class)) { - parameters[i] = getOrLookupCamelContext().getTypeConverter(); - } else { - // we also support @BeanInject and @PropertyInject annotations - Annotation[] anns = method.getParameterAnnotations()[i]; - if (anns.length == 1) { - // we dont assume there are multiple annotations on the same parameter so grab first - Annotation ann = anns[0]; - if (ann.annotationType() == PropertyInject.class) { - PropertyInject pi = (PropertyInject) ann; - Object result = getPostProcessorHelper().getInjectionPropertyValue(type, pi.value(), pi.defaultValue(), - null, null, null); - parameters[i] = result; - } else if (ann.annotationType() == BeanConfigInject.class) { - BeanConfigInject pi = (BeanConfigInject) ann; - Object result = getPostProcessorHelper().getInjectionBeanConfigValue(type, pi.value()); - parameters[i] = result; - } else if (ann.annotationType() == BeanInject.class) { - BeanInject bi = (BeanInject) ann; - Object result = getPostProcessorHelper().getInjectionBeanValue(type, bi.value()); - parameters[i] = result; - } - } else { - // okay attempt to default to singleton instances from the registry - Set<?> instances = getOrLookupCamelContext().getRegistry().findByType(type); - if (instances.size() == 1) { - parameters[i] = instances.iterator().next(); - } else if (instances.size() > 1) { - // there are multiple instances of the same type, so barf - throw new IllegalArgumentException( - "Multiple beans of the same type: " + type - + " exists in the Camel registry. Specify the bean name on @BeanInject to bind to a single bean, at the method: " - + method); - } - } - } - - // each parameter must be mapped - if (parameters[i] == null) { - int pos = i + 1; - throw new IllegalArgumentException("@BindToProperty cannot bind parameter #" + pos + " on method: " + method); - } - } - - return parameters; - } - private static boolean isComplexUserType(Class type) { // lets consider all non java, as complex types return type != null && !type.isPrimitive() && !type.getName().startsWith("java."); diff --git a/core/camel-core/src/test/java/org/apache/camel/impl/CamelBeanPostProcessorInjectorTest.java b/core/camel-core/src/test/java/org/apache/camel/impl/CamelBeanPostProcessorInjectorTest.java new file mode 100644 index 0000000..dc9793a --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/impl/CamelBeanPostProcessorInjectorTest.java @@ -0,0 +1,101 @@ +/* + * 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.impl; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.ExtendedCamelContext; +import org.apache.camel.PropertyInject; +import org.apache.camel.impl.engine.CamelPostProcessorHelper; +import org.apache.camel.spi.CamelBeanPostProcessor; +import org.apache.camel.spi.CamelBeanPostProcessorInjector; +import org.apache.camel.spi.CamelLogger; +import org.apache.camel.support.ObjectHelper; +import org.apache.camel.util.ReflectionHelper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class CamelBeanPostProcessorInjectorTest extends ContextTestSupport { + + private CamelBeanPostProcessor postProcessor; + private CamelPostProcessorHelper helper; + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + postProcessor = context.adapt(ExtendedCamelContext.class).getBeanPostProcessor(); + postProcessor.addCamelBeanPostProjectInjector(new MyInjector()); + helper = new CamelPostProcessorHelper(context); + } + + private class MyInjector implements CamelBeanPostProcessorInjector { + + @Override + public void onFieldInject(Field field, Object bean, String beanName) { + if (field.getName().equals("foo")) { + ReflectionHelper.setField(field, bean, "changed-foo"); + } + } + + @Override + public void onMethodInject(Method method, Object bean, String beanName) { + if (method.getName().equals("createLogger")) { + Object out = ObjectHelper.invokeMethod(method, bean, "changed-bar"); + context.getRegistry().bind(method.getName(), out); + } + } + } + + public class MyService { + + @PropertyInject(value = "myName", defaultValue = "Donald Duck") + private String name; + @PropertyInject(value = "myFoo", defaultValue = "myDefault") + private String foo; + + public String getName() { + return name; + } + + public String getFoo() { + return foo; + } + + public CamelLogger createLogger(String name) { + return new CamelLogger(name); + } + } + + @Test + public void testBeanPostInjector() throws Exception { + MyService service = new MyService(); + + postProcessor.postProcessBeforeInitialization(service, "service"); + postProcessor.postProcessAfterInitialization(service, "service"); + + Assertions.assertEquals("Donald Duck", service.getName()); + Assertions.assertEquals("changed-foo", service.getFoo()); + + CamelLogger logger = (CamelLogger) context.getRegistry().lookupByName("createLogger"); + Assertions.assertNotNull(logger); + } + +} diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc index 4a58944..ba8a54f 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc @@ -238,6 +238,40 @@ curl http://localhost:8080/hello Hello World% ---- +=== Dependency Injection in Java classes + +When running Camel applications with camel-jbang, then the runtime is `camel-main` based. This means +there is no Spring Boot, or Quarkus available. However, we have added support for using annotation +based dependency injection in Java classes. + +==== Using Camel dependency injection + +You can use the following Camel annotations (they work on all runtimes): + +- `@BindToRegistry` on class level to create an instance of the class and register in the xref:registry.adoc[Registry]. +- `@BeanInject` to dependency inject a bean on a class field. +- `@PropertyInject` to inject a xref:using-propertyplaceholder.adoc[property placeholder]. Such as a property defined in `application.properties`. +- `@BindToRegistry` on a method to create a bean by invoking the method. +- `@Converter` on class level to auto-register the xref:type-converter.adoc[type converters] from the class. + +==== Using Spring Boot dependency injection + +You can use the following Spring Boot annotations: + +- `@Component` or `@Service` on class level to create an instance of the class and register in the xref:registry.adoc[Registry]. +- `@Autowired` to dependency inject a bean on a class field. `@Qualifier` can be used to specify the bean id. +- `@Value` to inject a xref:using-propertyplaceholder.adoc[property placeholder]. Such as a property defined in `application.properties`. +- `@Bean` on a method to create a bean by invoking the method. + +==== Using Quarkus injection + +You can use the following Quarkus annotations: + +- `@ApplicationScoped` or `@Singleton` on class level to create an instance of the class and register in the xref:registry.adoc[Registry]. `@Named` can be used to specify the bean id. +- `@Inject` to dependency inject an bean on a class field. `@Named` can be used to specify the bean id. +- `@ConfigProperty` to inject a xref:using-propertyplaceholder.adoc[property placeholder]. Such as a property defined in `application.properties`. +- `@Produces` on a method to create a bean by invoking the method. `@Named` can be used to specify the bean id. + === Debugging You can debug both camel@apache/camel and your integration scripts by making use of the `--debug` flag provided by JBang: diff --git a/dsl/camel-dsl-support/src/main/java/org/apache/camel/dsl/support/CompilePostProcessor.java b/dsl/camel-dsl-support/src/main/java/org/apache/camel/dsl/support/CompilePostProcessor.java new file mode 100644 index 0000000..021a97f --- /dev/null +++ b/dsl/camel-dsl-support/src/main/java/org/apache/camel/dsl/support/CompilePostProcessor.java @@ -0,0 +1,44 @@ +/* + * 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.dsl.support; + +import org.apache.camel.CamelContext; + +/** + * Allows to plugin custom post processors that are processed after the DSL has loaded the source and compiled into a + * Java object. + * <p/> + * This is used to detect and handle {@link org.apache.camel.BindToRegistry} and {@link org.apache.camel.Converter} + * classes. + */ +public interface CompilePostProcessor { + + /** + * Invoked after the class has been compiled + * + * @param camelContext the camel context + * @param name the name of the resource/class + * @param clazz the class + * @param instance the object created as instance of the class (if any) + * @throws Exception is thrown if error during post-processing + */ + void postCompile( + CamelContext camelContext, String name, + Class<?> clazz, Object instance) + throws Exception; + +} diff --git a/dsl/camel-dsl-support/src/main/java/org/apache/camel/dsl/support/RouteBuilderLoaderSupport.java b/dsl/camel-dsl-support/src/main/java/org/apache/camel/dsl/support/RouteBuilderLoaderSupport.java index 11fbc8f..c961867 100644 --- a/dsl/camel-dsl-support/src/main/java/org/apache/camel/dsl/support/RouteBuilderLoaderSupport.java +++ b/dsl/camel-dsl-support/src/main/java/org/apache/camel/dsl/support/RouteBuilderLoaderSupport.java @@ -16,6 +16,10 @@ */ package org.apache.camel.dsl.support; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + import org.apache.camel.CamelContextAware; import org.apache.camel.ExtendedCamelContext; import org.apache.camel.RoutesBuilder; @@ -33,7 +37,7 @@ import org.apache.camel.support.RoutesBuilderLoaderSupport; */ public abstract class RouteBuilderLoaderSupport extends RoutesBuilderLoaderSupport { private final String extension; - + private final List<CompilePostProcessor> compilePostProcessors = new ArrayList<>(); private StartupStepRecorder recorder; protected RouteBuilderLoaderSupport(String extension) { @@ -46,6 +50,21 @@ public abstract class RouteBuilderLoaderSupport extends RoutesBuilderLoaderSuppo return extension; } + /** + * Gets the registered {@link CompilePostProcessor}. + */ + public List<CompilePostProcessor> getCompilePostProcessors() { + return compilePostProcessors; + } + + /** + * Add a custom {@link CompilePostProcessor} to handle specific post-processing after compiling the source into a + * Java object. + */ + public void addCompilePostProcessor(CompilePostProcessor preProcessor) { + this.compilePostProcessors.add(preProcessor); + } + @Override protected void doBuild() throws Exception { super.doBuild(); @@ -56,6 +75,21 @@ public abstract class RouteBuilderLoaderSupport extends RoutesBuilderLoaderSuppo } @Override + protected void doStart() throws Exception { + super.doStart(); + + if (getCamelContext() != null) { + // discover optional compile post-processors to be used + Set<CompilePostProcessor> pres = getCamelContext().getRegistry().findByType(CompilePostProcessor.class); + if (pres != null && !pres.isEmpty()) { + for (CompilePostProcessor pre : pres) { + addCompilePostProcessor(pre); + } + } + } + } + + @Override public RoutesBuilder loadRoutesBuilder(Resource resource) throws Exception { final RouteBuilder builder = doLoadRouteBuilder(resource); if (builder != null) { diff --git a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java b/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java index dd149db..c29e54d 100644 --- a/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java +++ b/dsl/camel-java-joor-dsl/src/main/java/org/apache/camel/dsl/java/joor/JavaRoutesBuilderLoader.java @@ -21,24 +21,15 @@ import java.io.InputStream; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.camel.BindToRegistry; -import org.apache.camel.CamelConfiguration; -import org.apache.camel.Configuration; -import org.apache.camel.Converter; -import org.apache.camel.ExtendedCamelContext; -import org.apache.camel.LoggingLevel; -import org.apache.camel.TypeConverterExists; import org.apache.camel.api.management.ManagedResource; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.dsl.support.CompilePostProcessor; import org.apache.camel.dsl.support.RouteBuilderLoaderSupport; -import org.apache.camel.spi.CamelBeanPostProcessor; import org.apache.camel.spi.Resource; -import org.apache.camel.spi.TypeConverterRegistry; import org.apache.camel.spi.annotations.RoutesLoader; import org.apache.camel.support.ResourceHelper; import org.apache.camel.util.FileUtil; import org.apache.camel.util.IOHelper; -import org.apache.camel.util.ObjectHelper; import org.joor.Reflect; @ManagedResource(description = "Managed JavaRoutesBuilderLoader") @@ -63,51 +54,16 @@ public class JavaRoutesBuilderLoader extends RouteBuilderLoaderSupport { Reflect ref = Reflect.compile(name, content).create(); Class<?> clazz = ref.type(); + Object obj = ref.get(); - if (clazz.getAnnotation(Converter.class) != null) { - TypeConverterRegistry tcr = getCamelContext().getTypeConverterRegistry(); - TypeConverterExists exists = tcr.getTypeConverterExists(); - LoggingLevel level = tcr.getTypeConverterExistsLoggingLevel(); - // force type converter to override as we could be re-loading - tcr.setTypeConverterExists(TypeConverterExists.Override); - tcr.setTypeConverterExistsLoggingLevel(LoggingLevel.OFF); - try { - tcr.addTypeConverters(clazz); - } finally { - tcr.setTypeConverterExists(exists); - tcr.setTypeConverterExistsLoggingLevel(level); - } - return null; + // support custom annotation scanning post compilation + // such as to register custom beans, type converters, etc. + for (CompilePostProcessor pre : getCompilePostProcessors()) { + pre.postCompile(getCamelContext(), name, clazz, obj); } - Object obj = ref.get(); if (obj instanceof RouteBuilder) { return (RouteBuilder) obj; - } else if (obj != null) { - BindToRegistry bir = obj.getClass().getAnnotation(BindToRegistry.class); - Configuration cfg = obj.getClass().getAnnotation(Configuration.class); - if (bir != null || cfg != null || obj instanceof CamelConfiguration) { - CamelBeanPostProcessor bpp = getCamelContext().adapt(ExtendedCamelContext.class).getBeanPostProcessor(); - if (bir != null && ObjectHelper.isNotEmpty(bir.value())) { - name = bir.value(); - } else if (cfg != null && ObjectHelper.isNotEmpty(cfg.value())) { - name = cfg.value(); - } - // to support hot reloading of beans then we need to enable unbind mode in bean post processor - bpp.setUnbindEnabled(true); - try { - // this class is a bean service which needs to be post processed and registered which happens - // automatic by the bean post processor - bpp.postProcessBeforeInitialization(obj, name); - bpp.postProcessAfterInitialization(obj, name); - } finally { - bpp.setUnbindEnabled(false); - } - if (obj instanceof CamelConfiguration) { - ((CamelConfiguration) obj).configure(getCamelContext()); - } - return null; - } } return null; } @@ -127,4 +83,5 @@ public class JavaRoutesBuilderLoader extends RouteBuilderLoaderSupport { ? matcher.group(1) + "." + name : name; } + } diff --git a/dsl/camel-kamelet-main/pom.xml b/dsl/camel-kamelet-main/pom.xml index c1b4f9f..9ae5ade 100644 --- a/dsl/camel-kamelet-main/pom.xml +++ b/dsl/camel-kamelet-main/pom.xml @@ -105,6 +105,38 @@ <artifactId>camel-catalog-console</artifactId> </dependency> + <!-- optional spring annotation support --> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-context</artifactId> + <version>${spring-version}</version> + <exclusions> + <exclusion> + <groupId>org.springframework</groupId> + <artifactId>spring-aop</artifactId> + </exclusion> + <exclusion> + <groupId>org.springframework</groupId> + <artifactId>spring-core</artifactId> + </exclusion> + <exclusion> + <groupId>org.springframework</groupId> + <artifactId>spring-expression</artifactId> + </exclusion> + </exclusions> + </dependency> + <!-- optional quarkus annotation support --> + <dependency> + <groupId>jakarta.enterprise</groupId> + <artifactId>jakarta.enterprise.cdi-api</artifactId> + <version>${jakarta-cdi-api-version}</version> + </dependency> + <dependency> + <groupId>org.eclipse.microprofile.config</groupId> + <artifactId>microprofile-config-api</artifactId> + <version>${microprofile-config-version}</version> + </dependency> + <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-timer</artifactId> diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/AnnotationDependencyInjection.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/AnnotationDependencyInjection.java new file mode 100644 index 0000000..6689408 --- /dev/null +++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/AnnotationDependencyInjection.java @@ -0,0 +1,286 @@ +/* + * 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.main; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.apache.camel.BindToRegistry; +import org.apache.camel.CamelConfiguration; +import org.apache.camel.CamelContext; +import org.apache.camel.Configuration; +import org.apache.camel.Converter; +import org.apache.camel.ExtendedCamelContext; +import org.apache.camel.LoggingLevel; +import org.apache.camel.NoSuchBeanException; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.TypeConverterExists; +import org.apache.camel.dsl.support.CompilePostProcessor; +import org.apache.camel.impl.engine.CamelPostProcessorHelper; +import org.apache.camel.spi.CamelBeanPostProcessor; +import org.apache.camel.spi.CamelBeanPostProcessorInjector; +import org.apache.camel.spi.Registry; +import org.apache.camel.spi.TypeConverterRegistry; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.ReflectionHelper; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +/** + * To enable camel/spring/quarkus based annotations for dependency injection when loading DSLs. + */ +public final class AnnotationDependencyInjection { + + private AnnotationDependencyInjection() { + } + + public static void initAnnotationBasedDependencyInjection(CamelContext context) { + Registry registry = context.getRegistry(); + CamelBeanPostProcessor cbbp = context.adapt(ExtendedCamelContext.class).getBeanPostProcessor(); + + // camel / common + registry.bind("CamelTypeConverterCompilePostProcessor", new TypeConverterCompilePostProcessor()); + registry.bind("CamelBindToRegistryCompilePostProcessor", new BindToRegistryCompilePostProcessor()); + // spring + registry.bind("SpringAnnotationCompilePostProcessor", new SpringAnnotationCompilePostProcessor()); + cbbp.addCamelBeanPostProjectInjector(new SpringBeanPostProcessorInjector(context)); + // quarkus + registry.bind("QuarkusAnnotationCompilePostProcessor", new QuarkusAnnotationCompilePostProcessor()); + cbbp.addCamelBeanPostProjectInjector(new QuarkusBeanPostProcessorInjector(context)); + } + + private static class TypeConverterCompilePostProcessor implements CompilePostProcessor { + + @Override + public void postCompile(CamelContext camelContext, String name, Class<?> clazz, Object instance) throws Exception { + if (clazz.isAnnotationPresent(Converter.class)) { + TypeConverterRegistry tcr = camelContext.getTypeConverterRegistry(); + TypeConverterExists exists = tcr.getTypeConverterExists(); + LoggingLevel level = tcr.getTypeConverterExistsLoggingLevel(); + // force type converter to override as we could be re-loading + tcr.setTypeConverterExists(TypeConverterExists.Override); + tcr.setTypeConverterExistsLoggingLevel(LoggingLevel.OFF); + try { + tcr.addTypeConverters(clazz); + } finally { + tcr.setTypeConverterExists(exists); + tcr.setTypeConverterExistsLoggingLevel(level); + } + } + } + + } + + private static class BindToRegistryCompilePostProcessor implements CompilePostProcessor { + + @Override + public void postCompile(CamelContext camelContext, String name, Class<?> clazz, Object instance) throws Exception { + BindToRegistry bir = instance.getClass().getAnnotation(BindToRegistry.class); + Configuration cfg = instance.getClass().getAnnotation(Configuration.class); + if (bir != null || cfg != null || instance instanceof CamelConfiguration) { + CamelBeanPostProcessor bpp = camelContext.adapt(ExtendedCamelContext.class).getBeanPostProcessor(); + if (bir != null && ObjectHelper.isNotEmpty(bir.value())) { + name = bir.value(); + } else if (cfg != null && ObjectHelper.isNotEmpty(cfg.value())) { + name = cfg.value(); + } + // to support hot reloading of beans then we need to enable unbind mode in bean post processor + bpp.setUnbindEnabled(true); + try { + // this class uses camels own annotations so the bind to registry happens + // automatic by the bean post processor + bpp.postProcessBeforeInitialization(instance, name); + bpp.postProcessAfterInitialization(instance, name); + } finally { + bpp.setUnbindEnabled(false); + } + if (instance instanceof CamelConfiguration) { + ((CamelConfiguration) instance).configure(camelContext); + } + } + } + + } + + private static class SpringAnnotationCompilePostProcessor implements CompilePostProcessor { + + @Override + public void postCompile(CamelContext camelContext, String name, Class<?> clazz, Object instance) throws Exception { + // @Component and @Service are the same + Component comp = clazz.getAnnotation(Component.class); + Service service = clazz.getAnnotation(Service.class); + if (comp != null || service != null) { + CamelBeanPostProcessor bpp = camelContext.adapt(ExtendedCamelContext.class).getBeanPostProcessor(); + if (comp != null && ObjectHelper.isNotEmpty(comp.value())) { + name = comp.value(); + } else if (service != null && ObjectHelper.isNotEmpty(service.value())) { + name = service.value(); + } + bindBean(camelContext, name, instance, true); + } + } + } + + private static class SpringBeanPostProcessorInjector implements CamelBeanPostProcessorInjector { + + private final CamelContext context; + private final CamelPostProcessorHelper helper; + + public SpringBeanPostProcessorInjector(CamelContext context) { + this.context = context; + this.helper = new CamelPostProcessorHelper(context); + } + + @Override + public void onFieldInject(Field field, Object bean, String beanName) { + Autowired autowired = field.getAnnotation(Autowired.class); + if (autowired != null) { + String name = null; + Qualifier qualifier = field.getAnnotation(Qualifier.class); + if (qualifier != null) { + name = qualifier.value(); + } + + try { + ReflectionHelper.setField(field, bean, + helper.getInjectionBeanValue(field.getType(), name)); + } catch (NoSuchBeanException e) { + if (autowired.required()) { + throw e; + } + // not required so ignore + } + } + Value value = field.getAnnotation(Value.class); + if (value != null) { + ReflectionHelper.setField(field, bean, + helper.getInjectionPropertyValue(field.getType(), value.value(), null, null, bean, beanName)); + } + } + + @Override + public void onMethodInject(Method method, Object bean, String beanName) { + Bean bi = method.getAnnotation(Bean.class); + if (bi != null) { + Object instance = helper.getInjectionBeanMethodValue(context, method, bean, beanName); + if (instance != null) { + String name = method.getName(); + if (bi.name().length > 0) { + name = bi.name()[0]; + } + bindBean(context, name, instance, false); + } + } + } + } + + private static class QuarkusAnnotationCompilePostProcessor implements CompilePostProcessor { + + @Override + public void postCompile(CamelContext camelContext, String name, Class<?> clazz, Object instance) throws Exception { + // @ApplicationScoped and @Singleton are considered the same + ApplicationScoped as = clazz.getAnnotation(ApplicationScoped.class); + Singleton ss = clazz.getAnnotation(Singleton.class); + if (as != null || ss != null) { + Named named = clazz.getAnnotation(Named.class); + if (named != null) { + name = named.value(); + } + bindBean(camelContext, name, instance, true); + } + } + } + + private static class QuarkusBeanPostProcessorInjector implements CamelBeanPostProcessorInjector { + + private final CamelContext context; + private final CamelPostProcessorHelper helper; + + public QuarkusBeanPostProcessorInjector(CamelContext context) { + this.context = context; + this.helper = new CamelPostProcessorHelper(context); + } + + @Override + public void onFieldInject(Field field, Object bean, String beanName) { + Inject inject = field.getAnnotation(Inject.class); + if (inject != null) { + String name = null; + Named named = field.getAnnotation(Named.class); + if (named != null) { + name = named.value(); + } + + ReflectionHelper.setField(field, bean, + helper.getInjectionBeanValue(field.getType(), name)); + } + ConfigProperty cp = field.getAnnotation(ConfigProperty.class); + if (cp != null) { + ReflectionHelper.setField(field, bean, + helper.getInjectionPropertyValue(field.getType(), cp.name(), cp.defaultValue(), null, bean, beanName)); + } + } + + @Override + public void onMethodInject(Method method, Object bean, String beanName) { + Produces produces = method.getAnnotation(Produces.class); + Named bi = method.getAnnotation(Named.class); + if (produces != null || bi != null) { + Object instance = helper.getInjectionBeanMethodValue(context, method, bean, beanName); + if (instance != null) { + String name = method.getName(); + if (bi != null && !bi.value().isBlank()) { + name = bi.value(); + } + bindBean(context, name, instance, false); + } + } + } + } + + private static void bindBean(CamelContext context, String name, Object instance, boolean postProcess) { + // to support hot reloading of beans then we need to enable unbind mode in bean post processor + Registry registry = context.getRegistry(); + CamelBeanPostProcessor bpp = context.adapt(ExtendedCamelContext.class).getBeanPostProcessor(); + bpp.setUnbindEnabled(true); + try { + // re-bind the bean to the registry + registry.unbind(name); + registry.bind(name, instance); + if (postProcess) { + bpp.postProcessBeforeInitialization(instance, name); + bpp.postProcessAfterInitialization(instance, name); + } + } catch (Exception e) { + throw RuntimeCamelException.wrapRuntimeException(e); + } finally { + bpp.setUnbindEnabled(false); + } + } + +} diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/DependencyDownloaderKamelet.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/DependencyDownloaderKamelet.java index f4155e3..269088b 100644 --- a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/DependencyDownloaderKamelet.java +++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/DependencyDownloaderKamelet.java @@ -16,21 +16,36 @@ */ package org.apache.camel.main; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; import org.apache.camel.RuntimeCamelException; +import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.kamelet.KameletComponent; +import org.apache.camel.dsl.yaml.YamlRoutesBuilderLoaderSupport; import org.apache.camel.spi.Resource; import org.apache.camel.spi.RouteTemplateLoaderListener; import org.apache.camel.support.service.ServiceHelper; import org.apache.camel.support.service.ServiceSupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.snakeyaml.engine.v2.nodes.Node; +import org.snakeyaml.engine.v2.nodes.NodeType; +import org.snakeyaml.engine.v2.nodes.ScalarNode; +import org.snakeyaml.engine.v2.nodes.SequenceNode; + +import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.nodeAt; /** * To automatic downloaded dependencies that Kamelets requires. */ final class DependencyDownloaderKamelet extends ServiceSupport implements CamelContextAware, RouteTemplateLoaderListener { - private final KameletYamlRoutes downloader = new KameletYamlRoutes("yaml"); + private final KameletDependencyDownloader downloader = new KameletDependencyDownloader("yaml"); private CamelContext camelContext; @Override @@ -77,4 +92,98 @@ final class DependencyDownloaderKamelet extends ServiceSupport implements CamelC } } } + + /** + * To automatic downloaded dependencies that Kamelets requires. + */ + private static class KameletDependencyDownloader extends YamlRoutesBuilderLoaderSupport implements CamelContextAware { + + private static final Logger LOG = LoggerFactory.getLogger(KameletDependencyDownloader.class); + private CamelContext camelContext; + private final Set<String> downloaded = new HashSet<>(); + + public KameletDependencyDownloader(String extension) { + super(extension); + } + + @Override + public CamelContext getCamelContext() { + return camelContext; + } + + @Override + public void setCamelContext(CamelContext camelContext) { + this.camelContext = camelContext; + } + + @Override + protected RouteBuilder builder(Node node, Resource resource) { + final List<String> dependencies = new ArrayList<>(); + + Node deps = nodeAt(node, "/spec/dependencies"); + if (deps != null && deps.getNodeType() == NodeType.SEQUENCE) { + SequenceNode sn = (SequenceNode) deps; + for (Node child : sn.getValue()) { + if (child.getNodeType() == NodeType.SCALAR) { + ScalarNode scn = (ScalarNode) child; + String dep = scn.getValue(); + if (dep != null) { + LOG.trace("Kamelet dependency: {}", dep); + dependencies.add(dep); + } + } + } + } + + downloadDependencies(dependencies); + + // need to fool and return an empty route builder + return new RouteBuilder() { + @Override + public void configure() throws Exception { + // noop + } + }; + } + + private void downloadDependencies(List<String> dependencies) { + final List<String> gavs = new ArrayList<>(); + for (String dep : dependencies) { + String gav = dep; + if (dep.startsWith("camel:")) { + // it's a known camel component + gav = "org.apache.camel:camel-" + dep.substring(6) + ":" + camelContext.getVersion(); + } + if (isValidGav(gav)) { + gavs.add(gav); + } + } + + if (!gavs.isEmpty()) { + for (String gav : gavs) { + MavenGav mg = MavenGav.parseGav(camelContext, gav); + DownloaderHelper.downloadDependency(camelContext, mg.getGroupId(), mg.getArtifactId(), mg.getVersion()); + downloaded.add(gav); + } + } + } + + private boolean isValidGav(String gav) { + if (downloaded.contains(gav)) { + // already downloaded + return false; + } + + // skip camel-core and camel-kamelet as they are already included + if (gav.contains("org.apache.camel:camel-core") || gav.contains("org.apache.camel:camel-kamelet:")) { + return false; + } + + MavenGav mg = MavenGav.parseGav(camelContext, gav); + boolean exists = DownloaderHelper.alreadyOnClasspath(camelContext, mg.getArtifactId(), mg.getVersion()); + // valid if not already on classpath + return !exists; + } + + } } diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java index 665c872..48a7167 100644 --- a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java +++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java @@ -31,6 +31,7 @@ import org.apache.camel.startup.jfr.FlightRecorderStartupStepRecorder; * A Main class for booting up Camel with Kamelet in standalone mode. */ public class KameletMain extends MainCommandLineSupport { + public static final String DEFAULT_KAMELETS_LOCATION = "classpath:/kamelets,github:apache:camel-kamelets/kamelets"; private static ClassLoader kameletClassLoader; @@ -163,6 +164,8 @@ public class KameletMain extends MainCommandLineSupport { answer.setRegistry(registry); // load camel component and custom health-checks answer.setLoadHealthChecks(true); + // annotation based dependency injection for camel/spring/quarkus annotations in DSLs and Java beans + AnnotationDependencyInjection.initAnnotationBasedDependencyInjection(answer); // embed HTTP server if port is specified Object port = getInitialProperties().get("camel.jbang.platform-http.port"); diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletYamlRoutes.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletYamlRoutes.java deleted file mode 100644 index a57422c..0000000 --- a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletYamlRoutes.java +++ /dev/null @@ -1,130 +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.main; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.apache.camel.CamelContext; -import org.apache.camel.CamelContextAware; -import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.dsl.yaml.YamlRoutesBuilderLoaderSupport; -import org.apache.camel.spi.Resource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.snakeyaml.engine.v2.nodes.Node; -import org.snakeyaml.engine.v2.nodes.NodeType; -import org.snakeyaml.engine.v2.nodes.ScalarNode; -import org.snakeyaml.engine.v2.nodes.SequenceNode; - -import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.nodeAt; - -/** - * Reuse the YAML DSL support for parsing Kamelets - */ -class KameletYamlRoutes extends YamlRoutesBuilderLoaderSupport implements CamelContextAware { - - private static final Logger LOG = LoggerFactory.getLogger(KameletYamlRoutes.class); - private CamelContext camelContext; - private final Set<String> downloaded = new HashSet<>(); - - public KameletYamlRoutes(String extension) { - super(extension); - } - - @Override - public CamelContext getCamelContext() { - return camelContext; - } - - @Override - public void setCamelContext(CamelContext camelContext) { - this.camelContext = camelContext; - } - - @Override - protected RouteBuilder builder(Node node, Resource resource) { - final List<String> dependencies = new ArrayList<>(); - - Node deps = nodeAt(node, "/spec/dependencies"); - if (deps != null && deps.getNodeType() == NodeType.SEQUENCE) { - SequenceNode sn = (SequenceNode) deps; - for (Node child : sn.getValue()) { - if (child.getNodeType() == NodeType.SCALAR) { - ScalarNode scn = (ScalarNode) child; - String dep = scn.getValue(); - if (dep != null) { - LOG.trace("Kamelet dependency: {}", dep); - dependencies.add(dep); - } - } - } - } - - downloadDependencies(dependencies); - - // need to fool and return an empty route builder - return new RouteBuilder() { - @Override - public void configure() throws Exception { - // noop - } - }; - } - - private void downloadDependencies(List<String> dependencies) { - final List<String> gavs = new ArrayList<>(); - for (String dep : dependencies) { - String gav = dep; - if (dep.startsWith("camel:")) { - // it's a known camel component - gav = "org.apache.camel:camel-" + dep.substring(6) + ":" + camelContext.getVersion(); - } - if (isValidGav(gav)) { - gavs.add(gav); - } - } - - if (!gavs.isEmpty()) { - for (String gav : gavs) { - MavenGav mg = MavenGav.parseGav(camelContext, gav); - DownloaderHelper.downloadDependency(camelContext, mg.getGroupId(), mg.getArtifactId(), mg.getVersion()); - downloaded.add(gav); - } - } - } - - private boolean isValidGav(String gav) { - if (downloaded.contains(gav)) { - // already downloaded - return false; - } - - // skip camel-core and camel-kamelet as they are already included - if (gav.contains("org.apache.camel:camel-core") || gav.contains("org.apache.camel:camel-kamelet:")) { - return false; - } - - MavenGav mg = MavenGav.parseGav(camelContext, gav); - boolean exists = DownloaderHelper.alreadyOnClasspath(camelContext, mg.getArtifactId(), mg.getVersion()); - // valid if not already on classpath - return !exists; - } - -} diff --git a/parent/pom.xml b/parent/pom.xml index d7f50a1..927c622 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -288,6 +288,7 @@ <javax-annotation-api-version>1.3.2</javax-annotation-api-version> <jakarta-mail-version>1.6.6</jakarta-mail-version> <javax-servlet-api-version>3.1.0</javax-servlet-api-version> + <jakarta-cdi-api-version>2.0.2</jakarta-cdi-api-version> <jakarta-api-version>2.1.5</jakarta-api-version> <jakarta.el-version>3.0.3</jakarta.el-version> <jakarta-jaxb-version>2.3.3</jakarta-jaxb-version>