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 54ac46dce8563d6ed816be099494940f1890dee7
Author: onders86 <ondersezgin+git...@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);

Reply via email to