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 <claus.ib...@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; + } + }