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 2020/02/08 11:42:01 UTC

[camel] branch master updated: CAMEL-14525: camel-main now supports binding beans to registry via the camel.beans. prefix

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

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


The following commit(s) were added to refs/heads/master by this push:
     new cedd55d  CAMEL-14525: camel-main now supports binding beans to registry via the camel.beans. prefix
cedd55d is described below

commit cedd55dbe01befb79aa68b2a7c96bf382506387e
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sat Feb 8 12:36:32 2020 +0100

    CAMEL-14525: camel-main now supports binding beans to registry via the camel.beans. prefix
---
 .../org/apache/camel/main/BaseMainSupport.java     |  48 ++++++++++
 .../main/{MainSedaTest.java => MainBeansTest.java} |  30 ++++--
 .../java/org/apache/camel/main/MainSedaTest.java   |  24 +++++
 .../camel/support/PropertyBindingSupport.java      | 101 ++++++++++++---------
 4 files changed, 152 insertions(+), 51 deletions(-)

diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
index 5432b77..cd5e5b6 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
@@ -22,6 +22,7 @@ import java.io.InputStream;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -58,6 +59,7 @@ import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.IOHelper;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.OrderedProperties;
+import org.apache.camel.util.PropertiesHelper;
 import org.apache.camel.util.StringHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -694,6 +696,7 @@ public abstract class BaseMainSupport extends ServiceSupport {
         Map<String, Object> hystrixProperties = new LinkedHashMap<>();
         Map<String, Object> resilience4jProperties = new LinkedHashMap<>();
         Map<String, Object> restProperties = new LinkedHashMap<>();
+        Map<String, Object> beansProperties = new LinkedHashMap<>();
         for (String key : prop.stringPropertyNames()) {
             if (key.startsWith("camel.context.")) {
                 // grab the value
@@ -719,8 +722,22 @@ public abstract class BaseMainSupport extends ServiceSupport {
                 String option = key.substring(11);
                 validateOptionAndValue(key, option, value);
                 restProperties.put(optionKey(option), value);
+            } else if (key.startsWith("camel.beans.")) {
+                // grab the value
+                String value = prop.getProperty(key);
+                String option = key.substring(12);
+                validateOptionAndValue(key, option, value);
+                beansProperties.put(optionKey(option), value);
             }
         }
+
+        // create beans first as they may be used later
+        if (!beansProperties.isEmpty()) {
+            LOG.debug("Creating and binding beans to registry from loaded properties: {}", beansProperties.size());
+            bindBeansToRegistry(camelContext, beansProperties, "camel.beans.",
+                    mainConfigurationProperties.isAutoConfigurationFailFast(), true, autoConfiguredProperties);
+        }
+
         if (!contextProperties.isEmpty()) {
             LOG.debug("Auto-configuring CamelContext from loaded properties: {}", contextProperties.size());
             setPropertiesOnTarget(camelContext, camelContext, contextProperties, "camel.context.",
@@ -761,6 +778,11 @@ public abstract class BaseMainSupport extends ServiceSupport {
         }
 
         // log which options was not set
+        if (!beansProperties.isEmpty()) {
+            beansProperties.forEach((k, v) -> {
+                LOG.warn("Property not auto-configured: camel.beans.{}={}", k, v);
+            });
+        }
         if (!contextProperties.isEmpty()) {
             contextProperties.forEach((k, v) -> {
                 LOG.warn("Property not auto-configured: camel.context.{}={} on bean: {}", k, v, camelContext);
@@ -789,6 +811,32 @@ public abstract class BaseMainSupport extends ServiceSupport {
         }
     }
 
+    private void bindBeansToRegistry(CamelContext camelContext, Map<String, Object> properties,
+                                     String optionPrefix, boolean failIfNotSet, boolean ignoreCase,
+                                     Map<String, String> autoConfiguredProperties) throws Exception {
+
+        // make defensive copy as we mutate the map
+        Set<String> keys = new LinkedHashSet<>(properties.keySet());
+        for (String key : keys) {
+            if (key.indexOf('.') == -1) {
+                // create beans first and then set properties
+                String name = key;
+                Object value = properties.remove(key);
+                Object bean = PropertyBindingSupport.resolveBean(camelContext, name, value);
+                if (bean == null) {
+                    throw new IllegalArgumentException("Cannot create/resolve bean with name " + name + " from value: " + value);
+                }
+                // register bean
+                camelContext.getRegistry().bind(name, bean);
+                autoConfiguredProperties.put(optionPrefix + key, value.toString());
+                // and then configure properties on the beans afterwards
+                Map<String, Object> config = PropertiesHelper.extractProperties(properties, key + ".");
+                setPropertiesOnTarget(camelContext, bean, config, optionPrefix + key + ".", failIfNotSet, ignoreCase, autoConfiguredProperties);
+                LOG.info("Binding bean: {} (type: {}) to the registry", key, ObjectHelper.classCanonicalName(bean));
+            }
+        }
+    }
+
     protected void autoConfigurationPropertiesComponent(CamelContext camelContext, Map<String, String> autoConfiguredProperties) throws Exception {
         // load properties
         Properties prop = camelContext.getPropertiesComponent().loadProperties(name -> name.startsWith("camel."));
diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainSedaTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainBeansTest.java
similarity index 62%
copy from core/camel-main/src/test/java/org/apache/camel/main/MainSedaTest.java
copy to core/camel-main/src/test/java/org/apache/camel/main/MainBeansTest.java
index 09ad4a1..a0aa9a1 100644
--- a/core/camel-main/src/test/java/org/apache/camel/main/MainSedaTest.java
+++ b/core/camel-main/src/test/java/org/apache/camel/main/MainBeansTest.java
@@ -22,24 +22,36 @@ import org.apache.camel.component.seda.SedaComponent;
 import org.junit.Assert;
 import org.junit.Test;
 
-public class MainSedaTest extends Assert {
+public class MainBeansTest extends Assert {
 
     @Test
-    public void testSedaMain() throws Exception {
+    public void testBindBeans() throws Exception {
+        MyFoo myFoo = new MyFoo();
+
         Main main = new Main();
         main.addRoutesBuilder(new MyRouteBuilder());
-        main.addProperty("camel.component.seda.defaultQueueFactory", "#class:org.apache.camel.main.MySedaBlockingQueueFactory");
-        main.addProperty("camel.component.seda.defaultQueueFactory.counter", "123");
+        main.bind("myFoolish", myFoo);
+
+        // create by class
+        main.addProperty("camel.beans.foo", "#class:org.apache.camel.main.MySedaBlockingQueueFactory");
+        main.addProperty("camel.beans.foo.counter", "123");
+
+        // lookup by type
+        main.addProperty("camel.beans.myfoo", "#type:org.apache.camel.main.MyFoo");
+        main.addProperty("camel.beans.myfoo.name", "Donkey");
         main.start();
 
         CamelContext camelContext = main.getCamelContext();
         assertNotNull(camelContext);
 
-        SedaComponent seda = camelContext.getComponent("seda", SedaComponent.class);
-        assertNotNull(seda);
-        assertTrue(seda.getDefaultQueueFactory() instanceof MySedaBlockingQueueFactory);
-        MySedaBlockingQueueFactory myBQF = (MySedaBlockingQueueFactory) seda.getDefaultQueueFactory();
+        Object foo = camelContext.getRegistry().lookupByName("foo");
+        assertNotNull(foo);
+
+        MySedaBlockingQueueFactory myBQF = camelContext.getRegistry().findByType(MySedaBlockingQueueFactory.class).iterator().next();
+        assertSame(foo, myBQF);
+
         assertEquals(123, myBQF.getCounter());
+        assertEquals("Donkey", myFoo.getName());
 
         main.stop();
     }
@@ -47,7 +59,7 @@ public class MainSedaTest extends Assert {
     public static class MyRouteBuilder extends RouteBuilder {
         @Override
         public void configure() throws Exception {
-            from("direct:start").to("seda:foo");
+            from("direct:start").to("mock:foo");
         }
     }
 
diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainSedaTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainSedaTest.java
index 09ad4a1..8172671 100644
--- a/core/camel-main/src/test/java/org/apache/camel/main/MainSedaTest.java
+++ b/core/camel-main/src/test/java/org/apache/camel/main/MainSedaTest.java
@@ -44,6 +44,30 @@ public class MainSedaTest extends Assert {
         main.stop();
     }
 
+    @Test
+    public void testSedaAutowireFromRegistryMain() throws Exception {
+        Main main = new Main();
+        main.addRoutesBuilder(new MyRouteBuilder());
+        main.addProperty("camel.beans.myqf", "#class:org.apache.camel.main.MySedaBlockingQueueFactory");
+        main.addProperty("camel.beans.myqf.counter", "123");
+        main.start();
+
+        CamelContext camelContext = main.getCamelContext();
+        assertNotNull(camelContext);
+
+        // the keys will be lower-cased
+        assertNotNull(camelContext.getRegistry().lookupByName("myqf"));
+
+        // seda will autowire from registry and discover the custom qf and use it
+        SedaComponent seda = camelContext.getComponent("seda", SedaComponent.class);
+        assertNotNull(seda);
+        assertTrue(seda.getDefaultQueueFactory() instanceof MySedaBlockingQueueFactory);
+        MySedaBlockingQueueFactory myBQF = (MySedaBlockingQueueFactory) seda.getDefaultQueueFactory();
+        assertEquals(123, myBQF.getCounter());
+
+        main.stop();
+    }
+
     public static class MyRouteBuilder extends RouteBuilder {
         @Override
         public void configure() throws Exception {
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java b/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java
index e01aa7d..0849675 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java
@@ -555,45 +555,7 @@ public final class PropertyBindingSupport {
     private static Object resolveValue(CamelContext context, Object target, String name, Object value,
                                        boolean ignoreCase, boolean fluentBuilder, boolean allowPrivateSetter) throws Exception {
         if (value instanceof String) {
-            if (value.toString().startsWith("#class:")) {
-                // its a new class to be created
-                String className = value.toString().substring(7);
-                String factoryMethod = null;
-                String parameters = null;
-                if (className.endsWith(")") && className.indexOf('(') != -1) {
-                    parameters = StringHelper.after(className, "(");
-                    parameters = parameters.substring(0, parameters.length() - 1); // clip last )
-                    className = StringHelper.before(className, "(");
-                }
-                if (className != null && className.indexOf('#') != -1) {
-                    factoryMethod = StringHelper.after(className, "#");
-                    className = StringHelper.before(className, "#");
-                }
-                Class<?> type = context.getClassResolver().resolveMandatoryClass(className);
-                if (factoryMethod != null) {
-                    value = context.getInjector().newInstance(type, factoryMethod);
-                } else if (parameters != null) {
-                    // special to support constructor parameters
-                    value = newInstanceConstructorParameters(context, type, parameters);
-                } else {
-                    value = context.getInjector().newInstance(type);
-                }
-                if (value == null) {
-                    throw new IllegalStateException("Cannot create instance of class: " + className);
-                }
-            } else if (value.toString().startsWith("#type:")) {
-                // its reference by type, so lookup the actual value and use it if there is only one instance in the registry
-                String typeName = value.toString().substring(6);
-                Class<?> type = context.getClassResolver().resolveMandatoryClass(typeName);
-                Set<?> types = context.getRegistry().findByType(type);
-                if (types.size() == 1) {
-                    value = types.iterator().next();
-                } else if (types.size() > 1) {
-                    throw new IllegalStateException("Cannot select single type: " + typeName + " as there are " + types.size() + " beans in the registry with this type");
-                } else {
-                    throw new IllegalStateException("Cannot select single type: " + typeName + " as there are no beans in the registry with this type");
-                }
-            } else if (value.toString().equals("#autowired")) {
+            if (value.toString().equals("#autowired")) {
                 // we should get the type from the setter
                 Method method = findBestSetterMethod(context, target.getClass(), name, fluentBuilder, allowPrivateSetter, ignoreCase);
                 if (method != null) {
@@ -609,9 +571,8 @@ public final class PropertyBindingSupport {
                 } else {
                     throw new IllegalStateException("Cannot find setter method with name: " + name + " on class: " + target.getClass().getName() + " to use for autowiring");
                 }
-            } else if (value.toString().startsWith("#bean:")) {
-                String key = value.toString().substring(6);
-                value = context.getRegistry().lookupByName(key);
+            } else {
+                value = resolveBean(context, name, value);
             }
         }
         return value;
@@ -957,4 +918,60 @@ public final class PropertyBindingSupport {
         return parameterType.isAssignableFrom(expectedType);
     }
 
+    /**
+     * Resolves the value as either a class, type or bean.
+     *
+     * @param camelContext       the camel context
+     * @param name               the name of the bean
+     * @param value              how to resolve the bean with a prefix of either class#:, type#: or bean#:
+     * @return the resolve bean
+     * @throws Exception is thrown if error resolving the bean, or if the value is invalid.
+     */
+    public static Object resolveBean(CamelContext camelContext, String name, Object value) throws Exception {
+        if (value.toString().startsWith("#class:")) {
+            // its a new class to be created
+            String className = value.toString().substring(7);
+            String factoryMethod = null;
+            String parameters = null;
+            if (className.endsWith(")") && className.indexOf('(') != -1) {
+                parameters = StringHelper.after(className, "(");
+                parameters = parameters.substring(0, parameters.length() - 1); // clip last )
+                className = StringHelper.before(className, "(");
+            }
+            if (className != null && className.indexOf('#') != -1) {
+                factoryMethod = StringHelper.after(className, "#");
+                className = StringHelper.before(className, "#");
+            }
+            Class<?> type = camelContext.getClassResolver().resolveMandatoryClass(className);
+            if (factoryMethod != null) {
+                value = camelContext.getInjector().newInstance(type, factoryMethod);
+            } else if (parameters != null) {
+                // special to support constructor parameters
+                value = newInstanceConstructorParameters(camelContext, type, parameters);
+            } else {
+                value = camelContext.getInjector().newInstance(type);
+            }
+            if (value == null) {
+                throw new IllegalStateException("Cannot create instance of class: " + className);
+            }
+        } else if (value.toString().startsWith("#type:")) {
+            // its reference by type, so lookup the actual value and use it if there is only one instance in the registry
+            String typeName = value.toString().substring(6);
+            Class<?> type = camelContext.getClassResolver().resolveMandatoryClass(typeName);
+            Set<?> types = camelContext.getRegistry().findByType(type);
+            if (types.size() == 1) {
+                value = types.iterator().next();
+            } else if (types.size() > 1) {
+                throw new IllegalStateException("Cannot select single type: " + typeName + " as there are " + types.size() + " beans in the registry with this type");
+            } else {
+                throw new IllegalStateException("Cannot select single type: " + typeName + " as there are no beans in the registry with this type");
+            }
+        } else if (value.toString().startsWith("#bean:")) {
+            String key = value.toString().substring(6);
+            value = camelContext.getRegistry().lookupByName(key);
+        }
+
+        return value;
+    }
+
 }