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 2019/11/11 18:33:40 UTC
[camel] 01/02: CAMEL-14153: Add support for constructor parameters
to property binding support - eg for camel-main configurations.
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 dad2e1f705acc816b933cb4c808403df9bdab575
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon Nov 11 19:19:26 2019 +0100
CAMEL-14153: Add support for constructor parameters to property binding support - eg for camel-main configurations.
---
.../test/java/org/apache/camel/support/Animal.java | 50 ++++
.../camel/support/PropertyBindingSupportTest.java | 34 +++
.../camel/support/PropertyBindingSupport.java | 258 +++++++++++++++++----
3 files changed, 293 insertions(+), 49 deletions(-)
diff --git a/core/camel-core/src/test/java/org/apache/camel/support/Animal.java b/core/camel-core/src/test/java/org/apache/camel/support/Animal.java
new file mode 100644
index 0000000..73ada4b
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/support/Animal.java
@@ -0,0 +1,50 @@
+/*
+ * 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 Animal {
+ private String name;
+ private boolean dangerous;
+
+ public Animal() {
+ }
+
+ public Animal(String name) {
+ this.name = name;
+ }
+
+ public Animal(boolean dangerous, int foo) {
+ throw new IllegalArgumentException("Should not be invoked");
+ }
+
+ public Animal(String name, boolean dangerous) {
+ this.name = name;
+ this.dangerous = dangerous;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isDangerous() {
+ return dangerous;
+ }
+
+ public void setDangerous(boolean dangerous) {
+ this.dangerous = dangerous;
+ }
+}
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 eb995d7..f0b2bec 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
@@ -346,9 +346,35 @@ public class PropertyBindingSupportTest extends ContextTestSupport {
}
}
+ @Test
+ public void testNestedClassConstructorParameterOneParameter() throws Exception {
+ Foo foo = new Foo();
+
+ PropertyBindingSupport.build().bind(context, foo, "name", "James");
+ PropertyBindingSupport.build().bind(context, foo, "animal", "#class:org.apache.camel.support.Animal('Tony Tiger')");
+ PropertyBindingSupport.build().bind(context, foo, "animal.dangerous", "true");
+
+ assertEquals("James", foo.getName());
+ assertEquals("Tony Tiger", foo.getAnimal().getName());
+ assertEquals(true, foo.getAnimal().isDangerous());
+ }
+
+ @Test
+ public void testNestedClassConstructorParameterTwoParameter() throws Exception {
+ Foo foo = new Foo();
+
+ PropertyBindingSupport.build().bind(context, foo, "name", "James");
+ PropertyBindingSupport.build().bind(context, foo, "animal", "#class:org.apache.camel.support.Animal('Donald Duck', false)");
+
+ assertEquals("James", foo.getName());
+ assertEquals("Donald Duck", foo.getAnimal().getName());
+ assertEquals(false, foo.getAnimal().isDangerous());
+ }
+
public static class Foo {
private String name;
private Bar bar = new Bar();
+ private Animal animal;
public String getName() {
return name;
@@ -365,6 +391,14 @@ public class PropertyBindingSupportTest extends ContextTestSupport {
public void setBar(Bar bar) {
this.bar = bar;
}
+
+ public Animal getAnimal() {
+ return animal;
+ }
+
+ public void setAnimal(Animal animal) {
+ this.animal = animal;
+ }
}
public static class Bar {
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 f88621e..ce63ff1 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
@@ -16,7 +16,9 @@
*/
package org.apache.camel.support;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -32,6 +34,7 @@ import org.apache.camel.PropertyBindingException;
import org.apache.camel.spi.GeneratedPropertyConfigurer;
import org.apache.camel.spi.PropertyConfigurer;
import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.StringQuoteHelper;
import static org.apache.camel.util.ObjectHelper.isNotEmpty;
@@ -49,7 +52,9 @@ import static org.apache.camel.util.ObjectHelper.isNotEmpty;
* <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.
* 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>.
+ * then you specify the method as shown: #class:com.foo.MyClassType#myFactoryMethod.
+ * 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>
* </ul>
*/
@@ -211,7 +216,7 @@ public final class PropertyBindingSupport {
/**
* Binds the properties to the target object, and removes the property that was bound from properties.
*
- * @return true if one or more properties was bound
+ * @return true if one or more properties was bound
*/
public boolean bind() {
// mandatory parameters
@@ -226,10 +231,10 @@ public final class PropertyBindingSupport {
/**
* Binds the properties to the target object, and removes the property that was bound from properties.
*
- * @param camelContext the camel context
- * @param target the target object
- * @param properties the properties where the bound properties will be removed from
- * @return true if one or more properties was bound
+ * @param camelContext the camel context
+ * @param target the target object
+ * @param properties the properties where the bound properties will be removed from
+ * @return true if one or more properties was bound
*/
public boolean bind(CamelContext camelContext, Object target, Map<String, Object> properties) {
CamelContext context = camelContext != null ? camelContext : this.camelContext;
@@ -248,11 +253,11 @@ public final class PropertyBindingSupport {
/**
* Binds the property to the target object.
*
- * @param camelContext the camel context
- * @param target the target object
- * @param key the property key
- * @param value the property value
- * @return true if the property was bound
+ * @param camelContext the camel context
+ * @param target the target object
+ * @param key the property key
+ * @param value the property value
+ * @return true if the property was bound
*/
public boolean bind(CamelContext camelContext, Object target, String key, Object value) {
org.apache.camel.util.ObjectHelper.notNull(camelContext, "camelContext");
@@ -282,10 +287,10 @@ public final class PropertyBindingSupport {
/**
* Callback when a property was autowired on a bean
*
- * @param target the targeted bean
- * @param propertyName the name of the property
- * @param propertyType the type of the property
- * @param value the property value
+ * @param target the targeted bean
+ * @param propertyName the name of the property
+ * @param propertyType the type of the property
+ * @param value the property value
*/
void onAutowire(Object target, String propertyName, Class propertyType, Object value);
@@ -297,9 +302,9 @@ public final class PropertyBindingSupport {
* This is used for convention over configuration to automatic configure resources such as DataSource, Amazon Logins and
* so on.
*
- * @param camelContext the camel context
- * @param target the target object
- * @return true if one ore more properties was auto wired
+ * @param camelContext the camel context
+ * @param target the target object
+ * @return true if one ore more properties was auto wired
*/
public static boolean autowireSingletonPropertiesFromRegistry(CamelContext camelContext, Object target) {
return autowireSingletonPropertiesFromRegistry(camelContext, target, false, false, null);
@@ -311,14 +316,14 @@ public final class PropertyBindingSupport {
* This is used for convention over configuration to automatic configure resources such as DataSource, Amazon Logins and
* so on.
*
- * @param camelContext the camel context
- * @param target the target object
- * @param bindNullOnly whether to only autowire if the property has no default value or has not been configured explicit
- * @param deepNesting whether to attempt to walk as deep down the object graph by creating new empty objects on the way if needed (Camel can only create
- * new empty objects if they have a default no-arg constructor, also mind that this may lead to creating many empty objects, even
- * if they will not have any objects autowired from the registry, so use this with caution)
- * @param callback optional callback when a property was auto wired
- * @return true if one ore more properties was auto wired
+ * @param camelContext the camel context
+ * @param target the target object
+ * @param bindNullOnly whether to only autowire if the property has no default value or has not been configured explicit
+ * @param deepNesting whether to attempt to walk as deep down the object graph by creating new empty objects on the way if needed (Camel can only create
+ * new empty objects if they have a default no-arg constructor, also mind that this may lead to creating many empty objects, even
+ * if they will not have any objects autowired from the registry, so use this with caution)
+ * @param callback optional callback when a property was auto wired
+ * @return true if one ore more properties was auto wired
*/
public static boolean autowireSingletonPropertiesFromRegistry(CamelContext camelContext, Object target,
boolean bindNullOnly, boolean deepNesting, OnAutowiring callback) {
@@ -422,11 +427,10 @@ public final class PropertyBindingSupport {
* the fluent builder {@link #build()} where each option can be customized, such as whether parameter
* should be removed, or whether options are mandatory etc.
*
- * @param camelContext the camel context
- * @param target the target object
- * @param properties the properties where the bound properties will be removed from
- * @return true if one or more properties was bound
- *
+ * @param camelContext the camel context
+ * @param target the target object
+ * @param properties the properties where the bound properties will be removed from
+ * @return true if one or more properties was bound
* @see #build()
*/
public static boolean bindProperties(CamelContext camelContext, Object target, Map<String, Object> properties) {
@@ -442,22 +446,22 @@ public final class PropertyBindingSupport {
* Binds the properties with the given prefix to the target object, and removes the property that was bound from properties.
* Note that the prefix is removed from the key before the property is bound.
*
- * @param camelContext the camel context
- * @param target the target object
- * @param properties the properties where the bound properties will be removed from
- * @param optionPrefix the prefix used to filter properties
- * @param ignoreCase whether to ignore case for property keys
- * @param removeParameter whether to remove bound parameters
- * @param mandatory whether all parameters must be bound
- * @param nesting whether nesting is in use
- * @param deepNesting whether deep nesting is in use, where Camel will attempt to walk as deep as possible by creating new objects in the OGNL graph if
- * a property has a setter and the object can be created from a default no-arg constructor.
- * @param fluentBuilder whether fluent builder is allowed as a valid getter/setter
- * @param allowPrivateSetter whether autowiring components allows to use private setter method when setting the value
- * @param reference whether reference parameter (syntax starts with #) is in use
- * @param placeholder whether to use Camels property placeholder to resolve placeholders on keys and values
- * @param configurer to use an optional {@link org.apache.camel.spi.PropertyConfigurer} to configure the properties
- * @return true if one or more properties was bound
+ * @param camelContext the camel context
+ * @param target the target object
+ * @param properties the properties where the bound properties will be removed from
+ * @param optionPrefix the prefix used to filter properties
+ * @param ignoreCase whether to ignore case for property keys
+ * @param removeParameter whether to remove bound parameters
+ * @param mandatory whether all parameters must be bound
+ * @param nesting whether nesting is in use
+ * @param deepNesting whether deep nesting is in use, where Camel will attempt to walk as deep as possible by creating new objects in the OGNL graph if
+ * a property has a setter and the object can be created from a default no-arg constructor.
+ * @param fluentBuilder whether fluent builder is allowed as a valid getter/setter
+ * @param allowPrivateSetter whether autowiring components allows to use private setter method when setting the value
+ * @param reference whether reference parameter (syntax starts with #) is in use
+ * @param placeholder whether to use Camels property placeholder to resolve placeholders on keys and values
+ * @param configurer to use an optional {@link org.apache.camel.spi.PropertyConfigurer} to configure the properties
+ * @return true if one or more properties was bound
*/
private static boolean doBindProperties(CamelContext camelContext, Object target, Map<String, Object> properties,
String optionPrefix, boolean ignoreCase, boolean removeParameter, boolean mandatory,
@@ -486,7 +490,7 @@ public final class PropertyBindingSupport {
// not resolve property placeholders eventually defined in the value before invoking
// the setter.
if (value instanceof String) {
- value = camelContext.resolvePropertyPlaceholders((String)value);
+ value = camelContext.resolvePropertyPlaceholders((String) value);
}
try {
value = resolveValue(camelContext, target, key, value, ignoreCase, fluentBuilder, allowPrivateSetter);
@@ -559,13 +563,22 @@ public final class PropertyBindingSupport {
// its a new class to be created
String className = value.toString().substring(7);
String factoryMethod = null;
- if (className.indexOf('#') != -1) {
+ 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);
}
@@ -800,5 +813,152 @@ public final class PropertyBindingSupport {
return parameter != null && parameter.trim().startsWith("#");
}
+ private static Object newInstanceConstructorParameters(CamelContext camelContext, Class<?> type, String parameters) throws Exception {
+ String[] params = StringQuoteHelper.splitSafeQuote(parameters, ',');
+ Constructor found = findMatchingConstructor(type.getConstructors(), 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.newInstance(arr);
+ }
+ return null;
+ }
+
+ /**
+ * Finds the best matching constructor for the given parameters.
+ * <p/>
+ * This implementation is similar to the logic in camel-bean.
+ *
+ * @param constructors the constructors
+ * @param params the parameters
+ * @return the constructor, or null if no matching constructor can be found
+ */
+ private static Constructor findMatchingConstructor(Constructor<?>[] constructors, String[] params) {
+ List<Constructor> candidates = new ArrayList<>();
+ Constructor fallbackCandidate = null;
+
+ for (Constructor ctr : constructors) {
+ if (ctr.getParameterCount() != params.length) {
+ continue;
+ }
+
+ boolean matches = true;
+ for (int i = 0; i < ctr.getParameterCount(); i++) {
+ String parameter = params[i];
+ if (parameter != null) {
+ // must trim
+ parameter = parameter.trim();
+ }
+
+ Class<?> parameterType = getValidParameterType(parameter);
+ Class<?> expectedType = ctr.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 = ctr;
+ matches = false;
+ break;
+ }
+
+ boolean matchingTypes = isParameterMatchingType(parameterType, expectedType);
+ if (!matchingTypes) {
+ matches = false;
+ break;
+ }
+ }
+ }
+
+ if (matches) {
+ candidates.add(ctr);
+ }
+ }
+
+ 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.
+ * <p/>
+ * This implementation is similar to the logic in camel-bean.
+ *
+ * @param value the value
+ * @return the parameter type the given value is being mapped as, or <tt>null</tt> if not valid.
+ */
+ private static Class<?> getValidParameterType(String value) {
+ if (org.apache.camel.util.ObjectHelper.isEmpty(value)) {
+ return null;
+ }
+
+ // trim value
+ value = value.trim();
+
+ // single quoted is valid
+ if (value.startsWith("'") && value.endsWith("'")) {
+ return String.class;
+ }
+
+ // double quoted is valid
+ if (value.startsWith("\"") && value.endsWith("\"")) {
+ return String.class;
+ }
+
+ // true or false is valid (boolean)
+ if (value.equals("true") || value.equals("false")) {
+ return Boolean.class;
+ }
+
+ // null is valid (to force a null value)
+ if (value.equals("null")) {
+ return Object.class;
+ }
+
+ // simple language tokens is valid
+ if (StringHelper.hasStartToken(value, "simple")) {
+ return Object.class;
+ }
+
+ // numeric is valid
+ boolean numeric = true;
+ for (char ch : value.toCharArray()) {
+ if (!Character.isDigit(ch)) {
+ numeric = false;
+ break;
+ }
+ }
+ if (numeric) {
+ return Number.class;
+ }
+
+ // not valid
+ return null;
+ }
+
+ private static boolean isParameterMatchingType(Class<?> parameterType, Class<?> expectedType) {
+ if (Number.class.equals(parameterType)) {
+ // number should match long/int/etc.
+ if (Integer.class.isAssignableFrom(expectedType) || Long.class.isAssignableFrom(expectedType)
+ || int.class.isAssignableFrom(expectedType) || long.class.isAssignableFrom(expectedType)) {
+ return true;
+ }
+ }
+ if (Boolean.class.equals(parameterType)) {
+ // boolean should match both Boolean and boolean
+ if (Boolean.class.isAssignableFrom(expectedType) || boolean.class.isAssignableFrom(expectedType)) {
+ return true;
+ }
+ }
+ return parameterType.isAssignableFrom(expectedType);
+ }
}