This is an automated email from the ASF dual-hosted git repository. onders pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
commit 22d7ec90b39cdbda1ad0478b06520a5d12720473 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Mon May 18 23:25:46 2020 +0300 CAMEL-15079: PropertyBindingSupport - Add support for parameters to factory method --- .../org/apache/camel/support/AnimalFactory.java | 29 +++++++ .../camel/support/PropertyBindingSupportTest.java | 36 ++++++++ .../camel/support/PropertyBindingSupport.java | 99 +++++++++++++++++++++- 3 files changed, 163 insertions(+), 1 deletion(-) diff --git a/core/camel-core/src/test/java/org/apache/camel/support/AnimalFactory.java b/core/camel-core/src/test/java/org/apache/camel/support/AnimalFactory.java new file mode 100644 index 0000000..ad5d1cc --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/support/AnimalFactory.java @@ -0,0 +1,29 @@ +/* + * 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.support; + +public class AnimalFactory { + + public static Animal createAnimal(String name, boolean dangerous) { + return new Animal(name, dangerous); + } + + public static Animal createAnimal(String name) { + return new Animal(name, true); + } + +} 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 b0fa8e6..8b96ed1 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 @@ -384,6 +384,42 @@ public class PropertyBindingSupportTest extends ContextTestSupport { } @Test + public void testNestedClassFactoryParameterOneParameter() throws Exception { + Foo foo = new Foo(); + + PropertyBindingSupport.build().bind(context, foo, "name", "James"); + PropertyBindingSupport.build().bind(context, foo, "animal", "#class:org.apache.camel.support.AnimalFactory#createAnimal('Tiger')"); + + assertEquals("James", foo.getName()); + assertEquals("Tiger", foo.getAnimal().getName()); + assertEquals(true, foo.getAnimal().isDangerous()); + } + + @Test + public void testNestedClassFactoryParameterTwoParameter() throws Exception { + Foo foo = new Foo(); + + PropertyBindingSupport.build().bind(context, foo, "name", "James"); + PropertyBindingSupport.build().bind(context, foo, "animal", "#class:org.apache.camel.support.AnimalFactory#createAnimal('Donald Duck', false)"); + + assertEquals("James", foo.getName()); + assertEquals("Donald Duck", foo.getAnimal().getName()); + assertEquals(false, foo.getAnimal().isDangerous()); + } + + @Test + public void testNestedClassFactoryParameterPlaceholder() throws Exception { + Foo foo = new Foo(); + + PropertyBindingSupport.build().bind(context, foo, "name", "James"); + PropertyBindingSupport.build().bind(context, foo, "animal", "#class:org.apache.camel.support.AnimalFactory#createAnimal('{{companyName}}', false)"); + + assertEquals("James", foo.getName()); + assertEquals("Acme", foo.getAnimal().getName()); + assertEquals(false, foo.getAnimal().isDangerous()); + } + + @Test public void testPropertiesOptionalKey() throws Exception { Foo foo = new Foo(); 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 df84e27..6ab4322 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 @@ -18,6 +18,7 @@ package org.apache.camel.support; import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -56,6 +57,8 @@ import static org.apache.camel.util.ObjectHelper.isNotEmpty; * <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. + * And if the factory method requires parameters they can be specified as follows: + * #class:com.foo.MyClassType#myFactoryMethod('Hello World', 5, true). * Or if you need to create the instance via constructor parameters then you can specify the parameters as shown: * #class:com.foo.MyClass('Hello World', 5, true)</li>. * <li>ignore case - Whether to ignore case for property keys<li> @@ -929,6 +932,92 @@ public final class PropertyBindingSupport { return candidates.size() == 1 ? candidates.get(0) : fallbackCandidate; } + private static Object newInstanceFactoryParameters(CamelContext camelContext, Class<?> type, String factoryMethod, String parameters) throws Exception { + String[] params = StringQuoteHelper.splitSafeQuote(parameters, ','); + Method found = findMatchingFactoryMethod(type.getMethods(), factoryMethod, params); + if (found != null) { + Object[] arr = new Object[found.getParameterCount()]; + for (int i = 0; i < found.getParameterCount(); i++) { + Class<?> paramType = found.getParameterTypes()[i]; + Object param = params[i]; + Object val = camelContext.getTypeConverter().convertTo(paramType, param); + // unquote text + if (val instanceof String) { + val = StringHelper.removeLeadingAndEndingQuotes((String) val); + } + arr[i] = val; + } + + return found.invoke(null, arr); + } + return null; + } + + /** + * Finds the best matching factory methods for the given parameters. + * <p/> + * This implementation is similar to the logic in camel-bean. + * + * @param methods the methods + * @param factoryMethod the name of the factory method + * @param params the parameters + * @return the constructor, or null if no matching constructor can be found + */ + private static Method findMatchingFactoryMethod(Method[] methods, String factoryMethod, String[] params) { + List<Method> candidates = new ArrayList<>(); + Method fallbackCandidate = null; + + for (Method method : methods) { + // must match factory method name + if (!factoryMethod.equals(method.getName())) { + continue; + } + // must be a public static method that returns something + if (!Modifier.isStatic(method.getModifiers()) || + !Modifier.isPublic(method.getModifiers()) || + method.getReturnType() == Void.TYPE) { + continue; + } + // must match number of parameters + if (method.getParameterCount() != params.length) { + continue; + } + + boolean matches = true; + for (int i = 0; i < method.getParameterCount(); i++) { + String parameter = params[i]; + if (parameter != null) { + // must trim + parameter = parameter.trim(); + } + + Class<?> parameterType = getValidParameterType(parameter); + Class<?> expectedType = method.getParameterTypes()[i]; + + if (parameterType != null && expectedType != null) { + // skip java.lang.Object type, when we have multiple possible methods we want to avoid it if possible + if (Object.class.equals(expectedType)) { + fallbackCandidate = method; + matches = false; + break; + } + + boolean matchingTypes = isParameterMatchingType(parameterType, expectedType); + if (!matchingTypes) { + matches = false; + break; + } + } + } + + if (matches) { + candidates.add(method); + } + } + + return candidates.size() == 1 ? candidates.get(0) : fallbackCandidate; + } + /** * Determines and maps the given value is valid according to the supported * values by the bean component. @@ -1030,7 +1119,15 @@ public final class PropertyBindingSupport { } Class<?> type = camelContext.getClassResolver().resolveMandatoryClass(className); if (factoryMethod != null) { - value = camelContext.getInjector().newInstance(type, factoryMethod); + if (parameters != null) { + // special to support factory method parameters + value = newInstanceFactoryParameters(camelContext, type, factoryMethod, parameters); + } else { + value = camelContext.getInjector().newInstance(type, factoryMethod); + } + if (value == null) { + throw new IllegalStateException("Cannot create bean instance using factory method: " + className + "#" + factoryMethod); + } } else if (parameters != null) { // special to support constructor parameters value = newInstanceConstructorParameters(camelContext, type, parameters);