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 7680c2a6ab930564c18bdea2070a9d1dc98b2bb6 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu May 30 12:10:42 2019 +0200 CAMEL-13557: Add property binding support to make it convenient to configure components and whatnot. --- .../support/PropertyBindingSupportMapTest.java | 186 +++++++++++++++++++++ .../apache/camel/support/IntrospectionSupport.java | 36 +++- .../camel/support/PropertyBindingSupport.java | 27 ++- 3 files changed, 244 insertions(+), 5 deletions(-) diff --git a/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportMapTest.java b/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportMapTest.java new file mode 100644 index 0000000..561d9dc --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportMapTest.java @@ -0,0 +1,186 @@ +/* + * 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 java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; + +import org.apache.camel.CamelContext; +import org.apache.camel.ContextTestSupport; +import org.apache.camel.PropertyBindingException; +import org.junit.Test; + +/** + * Unit test for PropertyBindingSupport + */ +public class PropertyBindingSupportMapTest extends ContextTestSupport { + + @Override + protected CamelContext createCamelContext() throws Exception { + CamelContext context = super.createCamelContext(); + + Company work1 = new Company(); + work1.setId(123); + work1.setName("Acme"); + context.getRegistry().bind("company1", work1); + Company work2 = new Company(); + work2.setId(456); + work2.setName("Acme 2"); + context.getRegistry().bind("company2", work2); + + Properties placeholders = new Properties(); + placeholders.put("companyName", "Acme"); + placeholders.put("committer", "rider"); + context.getPropertiesComponent().setInitialProperties(placeholders); + + return context; + } + + @Test + public void testPropertiesMap() throws Exception { + Foo foo = new Foo(); + + Map<String, Object> prop = new LinkedHashMap<>(); + prop.put("name", "James"); + prop.put("bar.age", "33"); + prop.put("bar.{{committer}}", "true"); + prop.put("bar.gold-customer", "true"); + prop.put("bar.works[acme]", "#bean:company1"); + prop.put("bar.works[burger]", "#bean:company2"); + + PropertyBindingSupport.bindProperties(context, foo, prop); + + assertEquals("James", foo.getName()); + assertEquals(33, foo.getBar().getAge()); + assertTrue(foo.getBar().isRider()); + assertTrue(foo.getBar().isGoldCustomer()); + assertEquals(2, foo.getBar().getWorks().size()); + assertEquals(123, foo.getBar().getWorks().get("acme").getId()); + assertEquals("Acme", foo.getBar().getWorks().get("acme").getName()); + assertEquals(456, foo.getBar().getWorks().get("burger").getId()); + assertEquals("Acme 2", foo.getBar().getWorks().get("burger").getName()); + } + + @Test + public void testPropertiesMapNested() throws Exception { + Foo foo = new Foo(); + + Map<String, Object> prop = new LinkedHashMap<>(); + prop.put("name", "James"); + prop.put("bar.age", "33"); + prop.put("bar.{{committer}}", "true"); + prop.put("bar.gold-customer", "true"); + prop.put("bar.works[acme]", "#bean:company1"); + prop.put("bar.works[acme].id", "666"); + prop.put("bar.works[burger]", "#bean:company2"); + prop.put("bar.works[burger].name", "I changed this"); + + PropertyBindingSupport.bindProperties(context, foo, prop); + + assertEquals("James", foo.getName()); + assertEquals(33, foo.getBar().getAge()); + assertTrue(foo.getBar().isRider()); + assertTrue(foo.getBar().isGoldCustomer()); + assertEquals(2, foo.getBar().getWorks().size()); + assertEquals(666, foo.getBar().getWorks().get("acme").getId()); + assertEquals("Acme", foo.getBar().getWorks().get("acme").getName()); + assertEquals(456, foo.getBar().getWorks().get("burger").getId()); + assertEquals("I changed this", foo.getBar().getWorks().get("burger").getName()); + } + + @Test + public void testPropertiesNotMap() throws Exception { + Foo foo = new Foo(); + + Map<String, Object> prop = new LinkedHashMap<>(); + prop.put("name", "James"); + prop.put("bar.age", "33"); + prop.put("bar.gold-customer[foo]", "true"); + + try { + PropertyBindingSupport.bindProperties(context, foo, prop); + fail("Should have thrown exception"); + } catch (PropertyBindingException e) { + assertEquals("bar.gold-customer[foo]", e.getPropertyName()); + IllegalArgumentException iae = assertIsInstanceOf(IllegalArgumentException.class, e.getCause()); + assertTrue(iae.getMessage().startsWith("Cannot set property: gold-customer[foo] as a Map because target bean is not a Map")); + } + } + + 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 Map<String, Company> works; // should auto-create this via the setter + private boolean goldCustomer; + + 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 Map<String, Company> getWorks() { + return works; + } + + public void setWorks(Map<String, Company> works) { + this.works = works; + } + + public boolean isGoldCustomer() { + return goldCustomer; + } + + public void setGoldCustomer(boolean goldCustomer) { + this.goldCustomer = goldCustomer; + } + } + +} + 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 fc62a37..700260a 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 @@ -524,18 +524,48 @@ public final class IntrospectionSupport { } /** - * This method supports two modes to set a property: + * This method supports three 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 + * 1. Setting a Map property where the property name refers to a map via name[aKey] where aKey is the map key to use. + * + * 2. 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 + * 3. 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 { + // does the property name include a mapped key, then we need to set the property as a map + if (name.contains("[") && name.endsWith("]")) { + int pos = name.indexOf('['); + String mapKey = name.substring(pos + 1, name.length() - 1); + String key = name.substring(0, pos); + + Object obj = IntrospectionSupport.getOrElseProperty(target, key, null); + if (obj == null) { + // it was supposed to be a map, but its null, so lets create a new map and set it automatically + obj = new LinkedHashMap<>(); + boolean hit = IntrospectionSupport.setProperty(context, target, key, obj); + if (!hit) { + throw new IllegalArgumentException("Cannot set property: " + name + " as a Map because target bean has no setter method for the Map"); + } + } + if (obj instanceof Map) { + Map map = (Map) obj; + if (context != null && refName != null && value == null) { + value = CamelContextHelper.lookup(context, refName); + } + map.put(mapKey, value); + return true; + } else { + // not a map + throw new IllegalArgumentException("Cannot set property: " + name + " as a Map because target bean is not a Map: " + target); + } + } + Class<?> clazz = target.getClass(); Collection<Method> setters; 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 a8ac7d2..1781c79 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 @@ -22,12 +22,13 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.camel.CamelContext; import org.apache.camel.PropertyBindingException; import static org.apache.camel.support.IntrospectionSupport.findSetterMethods; -import static org.apache.camel.support.IntrospectionSupport.getOrElseProperty; /** * A convenient support class for binding String valued properties to an instance which @@ -44,7 +45,7 @@ import static org.apache.camel.support.IntrospectionSupport.getOrElseProperty; */ public final class PropertyBindingSupport { - // TODO: Add support for Map/List + // TODO: Add support for Map/List in keys /** * To use a fluent builder style to configure this property binding support. @@ -359,6 +360,8 @@ public final class PropertyBindingSupport { } } + // TODO: support key without nested dots + // if name has dot then we need to OGNL walk it if (nesting) { if (name.indexOf('.') > 0) { @@ -440,6 +443,26 @@ public final class PropertyBindingSupport { return IntrospectionSupport.setProperty(context, context.getTypeConverter(), target, name, value, refName, fluentBuilder); } + private static Object getOrElseProperty(Object target, String property, Object defaultValue) { + String key = property; + String mapKey = null; + + // support maps in keys + if (property.contains("[") && property.endsWith("]")) { + int pos = property.indexOf('['); + mapKey = property.substring(pos + 1, property.length() - 1); + key = property.substring(0, pos); + } + + Object answer = IntrospectionSupport.getOrElseProperty(target, key, defaultValue); + if (answer instanceof Map && mapKey != null) { + Map map = (Map) answer; + answer = map.getOrDefault(mapKey, defaultValue); + } + + return answer; + } + private static Method findBestSetterMethod(Class clazz, String name, boolean fluentBuilder) { // is there a direct setter? Set<Method> candidates = findSetterMethods(clazz, name, false);