This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch 13557 in repository https://gitbox.apache.org/repos/asf/camel.git
commit f2cead10530208d2bd779a015673998a231b059f Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Wed May 22 15:47:57 2019 +0200 CAMEL-13557: Add property binding support to make it convenient to configure components and whatnot. --- .../java/org/apache/camel/main/MainSupport.java | 31 +---- .../camel/support/PropertyBindingSupportTest.java | 146 +++++++++++++++++++++ .../apache/camel/support/IntrospectionSupport.java | 58 +++++++- .../camel/support/PropertyBindingSupport.java | 47 +++++++ 4 files changed, 254 insertions(+), 28 deletions(-) diff --git a/core/camel-core/src/main/java/org/apache/camel/main/MainSupport.java b/core/camel-core/src/main/java/org/apache/camel/main/MainSupport.java index 0f3570e..fedb2cd 100644 --- a/core/camel-core/src/main/java/org/apache/camel/main/MainSupport.java +++ b/core/camel-core/src/main/java/org/apache/camel/main/MainSupport.java @@ -81,10 +81,9 @@ import org.apache.camel.spi.StreamCachingStrategy; import org.apache.camel.spi.ThreadPoolProfile; import org.apache.camel.spi.UnitOfWorkFactory; import org.apache.camel.spi.UuidGenerator; -import org.apache.camel.support.DefaultExchange; -import org.apache.camel.support.EndpointHelper; import org.apache.camel.support.IntrospectionSupport; import org.apache.camel.support.LifecycleStrategySupport; +import org.apache.camel.support.PropertyBindingSupport; import org.apache.camel.support.jsse.GlobalSSLContextParametersSupplier; import org.apache.camel.support.service.ServiceHelper; import org.apache.camel.support.service.ServiceSupport; @@ -1261,6 +1260,8 @@ public abstract class MainSupport extends ServiceSupport { Map<String, Object> properties = new LinkedHashMap<>(); IntrospectionSupport.getProperties(component, properties, null); + // TODO: Use PropertyBindingSupport to make it support this kind of use-case too + // lookup complex types properties.forEach((k, v) -> { // if the property has not been set and its a complex type (not simple or string etc) @@ -1343,34 +1344,10 @@ public abstract class MainSupport extends ServiceSupport { String name = entry.getKey(); Object value = entry.getValue(); - // if the name has dot's then its an OGNL expressions (so lets use simple language to walk down this ognl path) - boolean ognl = name.contains("."); - if (ognl) { - Language method = context.resolveLanguage("simple"); - String path = name.substring(0, name.lastIndexOf('.')); - Expression exp = method.createExpression("${body." + path + "}"); - Exchange dummy = new DefaultExchange(context); - dummy.getMessage().setBody(target); - Object newTarget = exp.evaluate(dummy, Object.class); - if (newTarget != null) { - target = newTarget; - name = name.substring(name.lastIndexOf('.') + 1); - } - } - String stringValue = value != null ? value.toString() : null; - boolean hit = false; LOG.debug("Setting property {} on {} with value {}", name, target, stringValue); - if (EndpointHelper.isReferenceParameter(stringValue)) { - hit = IntrospectionSupport.setProperty(context, context.getTypeConverter(), target, name, null, stringValue, true); - } else if (value != null) { - try { - hit = IntrospectionSupport.setProperty(context, context.getTypeConverter(), target, name, value); - } catch (IllegalArgumentException var12) { - hit = IntrospectionSupport.setProperty(context, context.getTypeConverter(), target, name, null, stringValue, true); - } - } + boolean hit = PropertyBindingSupport.bindProperty(context, target, name, stringValue); if (hit) { it.remove(); 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 new file mode 100644 index 0000000..c55745f --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportTest.java @@ -0,0 +1,146 @@ +/* + * 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; + +import org.apache.camel.CamelContext; +import org.apache.camel.ContextTestSupport; +import org.junit.Test; + +/** + * Unit test for PropertyBindingSupport with nested properties + */ +public class PropertyBindingSupportTest extends ContextTestSupport { + + @Override + protected CamelContext createCamelContext() throws Exception { + CamelContext context = super.createCamelContext(); + + Company work = new Company(); + work.setId(456); + work.setName("Acme"); + context.getRegistry().bind("myWork", work); + + return context; + } + + @Test + public void testNested() throws Exception { + Foo foo = new Foo(); + + PropertyBindingSupport.bindProperty(context, foo, "name", "James"); + PropertyBindingSupport.bindProperty(context, foo, "bar.age", "33"); + PropertyBindingSupport.bindProperty(context, foo, "bar.rider", "true"); + PropertyBindingSupport.bindProperty(context, foo, "bar.work.id", "123"); + PropertyBindingSupport.bindProperty(context, foo, "bar.work.name", "Acme"); + + assertEquals("James", foo.getName()); + assertEquals(33, foo.getBar().getAge()); + assertTrue(foo.getBar().isRider()); + assertEquals(123, foo.getBar().getWork().getId()); + assertEquals("Acme", foo.getBar().getWork().getName()); + } + + @Test + public void testNestedReference() throws Exception { + Foo foo = new Foo(); + + PropertyBindingSupport.bindProperty(context, foo, "name", "James"); + PropertyBindingSupport.bindProperty(context, foo, "bar.age", "33"); + PropertyBindingSupport.bindProperty(context, foo, "bar.rider", "true"); + PropertyBindingSupport.bindProperty(context, foo, "bar.work", "#myWork"); + + assertEquals("James", foo.getName()); + assertEquals(33, foo.getBar().getAge()); + assertTrue(foo.getBar().isRider()); + assertEquals(456, foo.getBar().getWork().getId()); + assertEquals("Acme", foo.getBar().getWork().getName()); + } + + public static class Foo { + private String name; + private Bar bar = new Bar(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Bar getBar() { + return bar; + } + + public void setBar(Bar bar) { + this.bar = bar; + } + } + + public static class Bar { + private int age; + private boolean rider; + private Company work; // has no default value but Camel can automatic create one if there is a setter + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public boolean isRider() { + return rider; + } + + public void setRider(boolean rider) { + this.rider = rider; + } + + public Company getWork() { + return work; + } + + public void setWork(Company work) { + this.work = work; + } + } + + public static class Company { + private int id; + private String name; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} + diff --git a/core/camel-support/src/main/java/org/apache/camel/support/IntrospectionSupport.java b/core/camel-support/src/main/java/org/apache/camel/support/IntrospectionSupport.java index 55f2612..28a1e3f 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/IntrospectionSupport.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/IntrospectionSupport.java @@ -513,12 +513,68 @@ public final class IntrospectionSupport { * 2. Setting a property that has not yet been resolved, the property will be resolved based on the suitable methods * found matching the property name on the {@code target} bean. For this mode to be triggered the parameters * {@code context} and {@code refName} must NOT be NULL, and {@code value} MUST be NULL. + */ + public static boolean setProperty(CamelContext context, TypeConverter typeConverter, Object target, String name, Object value, String refName, + boolean allowBuilderPattern) throws Exception { + return setProperty(context, typeConverter, target, name, value, refName, allowBuilderPattern, false); + } + /** + * This method supports two modes to set a property: + * + * 1. Setting a property that has already been resolved, this is the case when {@code context} and {@code refName} are + * NULL and {@code value} is non-NULL. * + * 2. Setting a property that has not yet been resolved, the property will be resolved based on the suitable methods + * found matching the property name on the {@code target} bean. For this mode to be triggered the parameters + * {@code context} and {@code refName} must NOT be NULL, and {@code value} MUST be NULL. */ - public static boolean setProperty(CamelContext context, TypeConverter typeConverter, Object target, String name, Object value, String refName, boolean allowBuilderPattern) throws Exception { + public static boolean setProperty(CamelContext context, TypeConverter typeConverter, Object target, String name, Object value, String refName, + boolean allowBuilderPattern, boolean allowNestedProperties) throws Exception { Class<?> clazz = target.getClass(); Collection<Method> setters; + // if name has dot then we need to OGNL walk it + if (allowNestedProperties && name.indexOf('.') > 0) { + String[] parts = name.split("\\."); + Object newTarget = target; + Class<?> newClass = clazz; + // we should only iterate until until 2nd last so we use -1 in the for loop + for (int i = 0; i < parts.length - 1; i++) { + String part = parts[i]; + Object prop = getOrElseProperty(newTarget, part, null); + if (prop == null) { + // okay is there a setter so we can create a new instance and set it automatic + Set<Method> newSetters = findSetterMethods(newClass, part, true); + if (newSetters.size() == 1) { + Method method = newSetters.iterator().next(); + Class<?> parameterType = method.getParameterTypes()[0]; + if (parameterType != null && org.apache.camel.util.ObjectHelper.hasDefaultPublicNoArgConstructor(parameterType)) { + Object instance = context.getInjector().newInstance(parameterType); + if (instance != null) { + org.apache.camel.support.ObjectHelper.invokeMethod(method, newTarget, instance); + newTarget = instance; + newClass = newTarget.getClass(); + } + } + } + } else { + newTarget = prop; + newClass = newTarget.getClass(); + } + } + // okay we found a nested property, then lets change to use that + target = newTarget; + clazz = newTarget.getClass(); + name = parts[parts.length - 1]; + if (value instanceof String) { + if (EndpointHelper.isReferenceParameter(value.toString())) { + // okay its a reference so swap to lookup this + refName = value.toString(); + value = null; + } + } + } + // we need to lookup the value from the registry if (context != null && refName != null && value == null) { setters = findSetterMethodsOrderedByParameterType(clazz, name, allowBuilderPattern); 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 new file mode 100644 index 0000000..b9d98bf --- /dev/null +++ b/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java @@ -0,0 +1,47 @@ +/** + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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; + +import org.apache.camel.CamelContext; + +import java.util.Map; + +/** + * A convenient support class for binding String valued properties to an instance which + * uses a set of conventions: + * <ul> + * <li>nested - Properties can be nested using the dot syntax (OGNL)</li> + * <li>reference by id - Values can refer to other beans by their id using # syntax</li> + * </ul> + */ +public final class PropertyBindingSupport { + + private PropertyBindingSupport() { + } + + public static boolean bindProperties(CamelContext camelContext, Object target, Map<String, Object> properties) throws Exception { + boolean answer = true; + for (Map.Entry<String, Object> entry : properties.entrySet()) { + answer &= bindProperty(camelContext, target, entry.getKey(), entry.getValue()); + } + return answer; + } + + public static boolean bindProperty(CamelContext camelContext, Object target, String name, Object value) throws Exception { + return IntrospectionSupport.setProperty(camelContext, camelContext.getTypeConverter(), target, name, value, null, true, true); + } +}