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
commit 7f8412f88bf517ac21f3522027f1bcebc06c32ac Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu Jun 27 19:01:34 2019 +0200 CAMEL-13695: camel-core - Injector allow to create beans via static factory methods --- .../org/apache/camel/cdi/CdiCamelInjector.java | 19 ++++++++++++++ .../camel/component/jcr/JcrConverterTest.java | 5 ++++ .../apache/camel/spring/spi/SpringInjector.java | 20 +++++++++++++++ .../main/java/org/apache/camel/spi/Injector.java | 10 ++++++++ .../apache/camel/impl/engine/DefaultInjector.java | 21 ++++++++++++++-- .../xml/AbstractCamelContextFactoryBeanTest.java | 5 ++++ .../org/apache/camel/impl/DefaultInjectorTest.java | 25 +++++++++++++++++++ .../camel/support/PropertyBindingSupportTest.java | 5 ++++ .../org/apache/camel/util/ReflectionInjector.java | 19 ++++++++++++++ .../camel/support/PropertyBindingSupport.java | 29 +++++++++++++++------- 10 files changed, 147 insertions(+), 11 deletions(-) diff --git a/components/camel-cdi/src/main/java/org/apache/camel/cdi/CdiCamelInjector.java b/components/camel-cdi/src/main/java/org/apache/camel/cdi/CdiCamelInjector.java index 582b3ee..3372793 100644 --- a/components/camel-cdi/src/main/java/org/apache/camel/cdi/CdiCamelInjector.java +++ b/components/camel-cdi/src/main/java/org/apache/camel/cdi/CdiCamelInjector.java @@ -16,8 +16,11 @@ */ package org.apache.camel.cdi; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import javax.enterprise.inject.spi.BeanManager; +import org.apache.camel.RuntimeCamelException; import org.apache.camel.spi.Injector; import static org.apache.camel.cdi.BeanManagerHelper.getReferenceByType; @@ -39,6 +42,22 @@ final class CdiCamelInjector implements Injector { } @Override + public <T> T newInstance(Class<T> type, String factoryMethod) { + T answer = null; + try { + // lookup factory method + Method fm = type.getMethod(factoryMethod); + if (Modifier.isStatic(fm.getModifiers()) && Modifier.isPublic(fm.getModifiers()) && fm.getReturnType() == type) { + Object obj = fm.invoke(null); + answer = type.cast(obj); + } + } catch (Exception e) { + throw new RuntimeCamelException("Error invoking factory method: " + factoryMethod + " on class: " + type, e); + } + return answer; + } + + @Override public <T> T newInstance(Class<T> type, boolean postProcessBean) { return getReferenceByType(manager, type) .orElseGet(() -> injector.newInstance(type, postProcessBean)); diff --git a/components/camel-jcr/src/test/java/org/apache/camel/component/jcr/JcrConverterTest.java b/components/camel-jcr/src/test/java/org/apache/camel/component/jcr/JcrConverterTest.java index ac1f35f..addd428 100644 --- a/components/camel-jcr/src/test/java/org/apache/camel/component/jcr/JcrConverterTest.java +++ b/components/camel-jcr/src/test/java/org/apache/camel/component/jcr/JcrConverterTest.java @@ -54,6 +54,11 @@ public class JcrConverterTest extends Assert { } @Override + public <T> T newInstance(Class<T> type, String factoryMethod) { + return null; + } + + @Override public <T> T newInstance(Class<T> type, boolean postProcessBean) { return ObjectHelper.newInstance(type); } diff --git a/components/camel-spring/src/main/java/org/apache/camel/spring/spi/SpringInjector.java b/components/camel-spring/src/main/java/org/apache/camel/spring/spi/SpringInjector.java index 7d50435..56efd9e 100644 --- a/components/camel-spring/src/main/java/org/apache/camel/spring/spi/SpringInjector.java +++ b/components/camel-spring/src/main/java/org/apache/camel/spring/spi/SpringInjector.java @@ -16,6 +16,10 @@ */ package org.apache.camel.spring.spi; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.apache.camel.RuntimeCamelException; import org.apache.camel.spi.Injector; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ConfigurableApplicationContext; @@ -37,6 +41,22 @@ public class SpringInjector implements Injector { } @Override + public <T> T newInstance(Class<T> type, String factoryMethod) { + T answer = null; + try { + // lookup factory method + Method fm = type.getMethod(factoryMethod); + if (Modifier.isStatic(fm.getModifiers()) && Modifier.isPublic(fm.getModifiers()) && fm.getReturnType() == type) { + Object obj = fm.invoke(null); + answer = type.cast(obj); + } + } catch (Exception e) { + throw new RuntimeCamelException("Error invoking factory method: " + factoryMethod + " on class: " + type, e); + } + return answer; + } + + @Override public <T> T newInstance(Class<T> type, boolean postProcessBean) { Object value; if (postProcessBean) { diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/Injector.java b/core/camel-api/src/main/java/org/apache/camel/spi/Injector.java index 81c7fa1..d37d8b1 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/Injector.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/Injector.java @@ -33,6 +33,16 @@ public interface Injector { <T> T newInstance(Class<T> type); /** + * Instantiates a new instance of the given type by using the factory method + * (this will not perform bean post processing) + * + * @param type the type of object to create + * @param factoryMethod to create the new instance via factory method which must be public static and return the type + * @return a newly created instance + */ + <T> T newInstance(Class<T> type, String factoryMethod); + + /** * Instantiates a new instance of the given type; possibly injecting values * into the object in the process (bean post processing if enabled) * diff --git a/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultInjector.java b/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultInjector.java index eee18ed..2b8f8de 100644 --- a/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultInjector.java +++ b/core/camel-base/src/main/java/org/apache/camel/impl/engine/DefaultInjector.java @@ -16,6 +16,9 @@ */ package org.apache.camel.impl.engine; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + import org.apache.camel.CamelContext; import org.apache.camel.ExtendedCamelContext; import org.apache.camel.RuntimeCamelException; @@ -27,8 +30,6 @@ import org.apache.camel.support.ObjectHelper; * A default implementation of {@link Injector} which just uses reflection to * instantiate new objects using their zero argument constructor, * and then performing bean post processing using {@link CamelBeanPostProcessor}. - * <p/> - * For more complex implementations try the Spring or Guice implementations. */ public class DefaultInjector implements Injector { @@ -45,6 +46,22 @@ public class DefaultInjector implements Injector { } @Override + public <T> T newInstance(Class<T> type, String factoryMethod) { + T answer = null; + try { + // lookup factory method + Method fm = type.getMethod(factoryMethod); + if (Modifier.isStatic(fm.getModifiers()) && Modifier.isPublic(fm.getModifiers()) && fm.getReturnType() == type) { + Object obj = fm.invoke(null); + answer = type.cast(obj); + } + } catch (Exception e) { + throw new RuntimeCamelException("Error invoking factory method: " + factoryMethod + " on class: " + type, e); + } + return answer; + } + + @Override public <T> T newInstance(Class<T> type, boolean postProcessBean) { T answer = ObjectHelper.newInstance(type); if (answer != null && postProcessBean) { diff --git a/core/camel-core-xml/src/test/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBeanTest.java b/core/camel-core-xml/src/test/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBeanTest.java index 8bfcdf1..375dd89 100644 --- a/core/camel-core-xml/src/test/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBeanTest.java +++ b/core/camel-core-xml/src/test/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBeanTest.java @@ -69,6 +69,11 @@ public class AbstractCamelContextFactoryBeanTest { } @Override + public <T> T newInstance(Class<T> type, String factoryMethod) { + return null; + } + + @Override public <T> T newInstance(Class<T> type, boolean postProcessBean) { return ObjectHelper.newInstance(type); } diff --git a/core/camel-core/src/test/java/org/apache/camel/impl/DefaultInjectorTest.java b/core/camel-core/src/test/java/org/apache/camel/impl/DefaultInjectorTest.java index 6d128d7..9eec2e6 100644 --- a/core/camel-core/src/test/java/org/apache/camel/impl/DefaultInjectorTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/impl/DefaultInjectorTest.java @@ -40,6 +40,18 @@ public class DefaultInjectorTest extends Assert { assertEquals("WorldWorld", reply); } + @Test + public void testDefaultInjectorFactory() throws Exception { + CamelContext context = new DefaultCamelContext(); + context.start(); + + // use the injector (will use the default) + MyOtherBean bean = context.getInjector().newInstance(MyOtherBean.class, "getInstance"); + + Object reply = bean.doSomething("World"); + assertEquals("WorldWorld", reply); + } + public static class MyBean { @Produce("language:simple:${body}${body}") @@ -50,4 +62,17 @@ public class DefaultInjectorTest extends Assert { } } + public static class MyOtherBean { + + private static MyOtherBean me = new MyOtherBean(); + + public static MyOtherBean getInstance() { + return me; + } + + public Object doSomething(String body) { + return body + body; + } + } + } diff --git a/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportTest.java b/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportTest.java index 588f3ec..5c751df 100644 --- a/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportTest.java @@ -298,6 +298,11 @@ public class PropertyBindingSupportTest extends ContextTestSupport { } @Override + public <T> T newInstance(Class<T> type, String factoryMethod) { + return null; + } + + @Override public <T> T newInstance(Class<T> type, boolean postProcessBean) { return null; } diff --git a/core/camel-core/src/test/java/org/apache/camel/util/ReflectionInjector.java b/core/camel-core/src/test/java/org/apache/camel/util/ReflectionInjector.java index ed081a9..b7fc377 100644 --- a/core/camel-core/src/test/java/org/apache/camel/util/ReflectionInjector.java +++ b/core/camel-core/src/test/java/org/apache/camel/util/ReflectionInjector.java @@ -16,6 +16,10 @@ */ package org.apache.camel.util; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.apache.camel.RuntimeCamelException; import org.apache.camel.spi.Injector; import org.apache.camel.support.ObjectHelper; @@ -32,6 +36,21 @@ public class ReflectionInjector implements Injector { } @Override + public <T> T newInstance(Class<T> type, String factoryMethod) { + T answer = null; + try { + // lookup factory method + Method fm = type.getMethod(factoryMethod); + if (Modifier.isStatic(fm.getModifiers()) && Modifier.isPublic(fm.getModifiers()) && fm.getReturnType() == type) { + answer = (T) fm.invoke(null); + } + } catch (Exception e) { + throw new RuntimeCamelException("Error invoking factory method: " + factoryMethod + " on class: " + type, e); + } + return answer; + } + + @Override public <T> T newInstance(Class<T> type, boolean postProcessBean) { return ObjectHelper.newInstance(type); // does not support post processing 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 1eaecb7..975fea8 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 @@ -26,8 +26,8 @@ import java.util.Map; import java.util.Set; import org.apache.camel.CamelContext; -import org.apache.camel.NoSuchBeanException; import org.apache.camel.PropertyBindingException; +import org.apache.camel.util.StringHelper; import static org.apache.camel.support.IntrospectionSupport.findSetterMethods; import static org.apache.camel.util.ObjectHelper.isNotEmpty; @@ -44,7 +44,9 @@ import static org.apache.camel.util.ObjectHelper.isNotEmpty; * <li>reference by bean id - Values can refer to other beans in the registry by prefixing with with # or #bean: eg #myBean or #bean:myBean</li> * <li>reference by type - Values can refer to singleton beans by their type in the registry by prefixing with #type: syntax, eg #type:com.foo.MyClassType</li> * <li>autowire by type - Values can refer to singleton beans by auto wiring by setting the value to #autowired</li> - * <li>reference new class - Values can refer to creating new beans by their class name by prefixing with #class, eg #class:com.foo.MyClassType</li> + * <li>reference new class - Values can refer to creating new beans by their class name by prefixing with #class, eg #class:com.foo.MyClassType. + * The class is created using a default no-arg constructor, however if you need to create the instance via a factory method + * then you specify the method as shown: #class:com.foo.MyClassType#myFactoryMethod</li>. * <li>ignore case - Whether to ignore case for property keys<li> * </ul> * <p/> @@ -549,10 +551,19 @@ public final class PropertyBindingSupport { if (value.toString().startsWith("#class:")) { // its a new class to be created String className = value.toString().substring(7); + String factoryMethod = null; + if (className.indexOf('#') != -1) { + factoryMethod = StringHelper.after(className, "#"); + className = StringHelper.before(className, "#"); + } Class<?> type = context.getClassResolver().resolveMandatoryClass(className); - value = context.getInjector().newInstance(type); + if (factoryMethod != null) { + value = context.getInjector().newInstance(type, factoryMethod); + } else { + value = context.getInjector().newInstance(type); + } if (value == null) { - throw new IllegalArgumentException("Cannot create instance of class: " + className); + 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 @@ -562,9 +573,9 @@ public final class PropertyBindingSupport { if (types.size() == 1) { value = types.iterator().next(); } else if (types.size() > 1) { - throw new IllegalArgumentException("Cannot select single type: " + typeName + " as there are " + types.size() + " beans in the registry with this type"); + throw new IllegalStateException("Cannot select single type: " + typeName + " as there are " + types.size() + " beans in the registry with this type"); } else { - throw new IllegalArgumentException("Cannot select single type: " + typeName + " as there are no beans in the registry with this type"); + 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")) { // we should get the type from the setter @@ -575,12 +586,12 @@ public final class PropertyBindingSupport { if (types.size() == 1) { value = types.iterator().next(); } else if (types.size() > 1) { - throw new IllegalArgumentException("Cannot select single type: " + parameterType + " as there are " + types.size() + " beans in the registry with this type"); + throw new IllegalStateException("Cannot select single type: " + parameterType + " as there are " + types.size() + " beans in the registry with this type"); } else { - throw new IllegalArgumentException("Cannot select single type: " + parameterType + " as there are no beans in the registry with this type"); + throw new IllegalStateException("Cannot select single type: " + parameterType + " as there are no beans in the registry with this type"); } } else { - throw new IllegalArgumentException("Cannot find setter method with name: " + name + " on class: " + target.getClass().getName() + " to use for autowiring"); + 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:")) { // okay its a reference so swap to lookup this which is already supported in IntrospectionSupport