You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2022/03/12 06:18:42 UTC

[camel] 07/11: CAMEL-17571: camel-jbang - Support for spring @Bean annotations in custom beans

This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch CAMEL-17571
in repository https://gitbox.apache.org/repos/asf/camel.git

commit f1c6b5dc7e6bffeb3e57e99871936ff86730af96
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Fri Mar 11 18:34:17 2022 +0100

    CAMEL-17571: camel-jbang - Support for spring @Bean annotations in custom beans
---
 .../impl/engine/CamelPostProcessorHelper.java      | 89 ++++++++++++++++++++++
 .../impl/engine/DefaultCamelBeanPostProcessor.java | 85 +--------------------
 .../apache/camel/main/SpringAnnotationSupport.java | 22 +++++-
 3 files changed, 114 insertions(+), 82 deletions(-)

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..a6a5654 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,6 +57,9 @@ import org.apache.camel.util.ObjectHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.camel.support.ObjectHelper.invokeMethod;
+import static org.apache.camel.util.ObjectHelper.isEmpty;
+
 /**
  * A helper class for Camel based injector or bean post processing hooks.
  */
@@ -402,6 +411,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 15ad1af..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,10 +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.CamelBeanPostProcessorInjector;
-import org.apache.camel.spi.Registry;
 import org.apache.camel.support.DefaultEndpoint;
 import org.apache.camel.util.ReflectionHelper;
 import org.slf4j.Logger;
@@ -515,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);
@@ -532,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);
@@ -559,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/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/SpringAnnotationSupport.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/SpringAnnotationSupport.java
index 1186ccd..881e4b3 100644
--- a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/SpringAnnotationSupport.java
+++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/SpringAnnotationSupport.java
@@ -31,6 +31,7 @@ import org.apache.camel.util.ReflectionHelper;
 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;
 
@@ -114,7 +115,26 @@ public final class SpringAnnotationSupport {
 
         @Override
         public void onMethodInject(Method method, Object bean, String beanName) {
-            // TODO; @Bean
+            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() != null && bi.name().length > 0) {
+                        name = bi.name()[0];
+                    }
+                    // to support hot reloading of beans then we need to enable unbind mode in bean post processor
+                    CamelBeanPostProcessor bpp = context.adapt(ExtendedCamelContext.class).getBeanPostProcessor();
+                    bpp.setUnbindEnabled(true);
+                    try {
+                        // re-bind the bean to the registry
+                        context.getRegistry().unbind(name);
+                        context.getRegistry().bind(name, instance);
+                    } finally {
+                        bpp.setUnbindEnabled(false);
+                    }
+                }
+            }
         }
     }
 }