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 e86896fa284842d5e66120d1390ab1888aa408e2 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu May 30 14:28:17 2019 +0200 CAMEL-13557: Add property binding support to make it convenient to configure components and whatnot. --- ...st.java => PropertyBindingSupportListTest.java} | 67 +++++++++++----------- .../support/PropertyBindingSupportMapTest.java | 2 +- .../apache/camel/support/IntrospectionSupport.java | 40 ++++++++++--- .../camel/support/PropertyBindingSupport.java | 29 ++++++++-- 4 files changed, 91 insertions(+), 47 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/PropertyBindingSupportListTest.java similarity index 70% copy from core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportMapTest.java copy to core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportListTest.java index f46e976..5579c64 100644 --- a/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportMapTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportListTest.java @@ -17,6 +17,7 @@ package org.apache.camel.support; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Properties; @@ -28,7 +29,7 @@ import org.junit.Test; /** * Unit test for PropertyBindingSupport */ -public class PropertyBindingSupportMapTest extends ContextTestSupport { +public class PropertyBindingSupportListTest extends ContextTestSupport { @Override protected CamelContext createCamelContext() throws Exception { @@ -52,7 +53,7 @@ public class PropertyBindingSupportMapTest extends ContextTestSupport { } @Test - public void testPropertiesMap() throws Exception { + public void testPropertiesList() throws Exception { Foo foo = new Foo(); Map<String, Object> prop = new LinkedHashMap<>(); @@ -60,8 +61,8 @@ public class PropertyBindingSupportMapTest extends ContextTestSupport { 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"); + prop.put("bar.works[0]", "#bean:company1"); + prop.put("bar.works[1]", "#bean:company2"); PropertyBindingSupport.bindProperties(context, foo, prop); @@ -70,14 +71,14 @@ public class PropertyBindingSupportMapTest extends ContextTestSupport { 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()); + assertEquals(123, foo.getBar().getWorks().get(0).getId()); + assertEquals("Acme", foo.getBar().getWorks().get(0).getName()); + assertEquals(456, foo.getBar().getWorks().get(1).getId()); + assertEquals("Acme 2", foo.getBar().getWorks().get(1).getName()); } @Test - public void testPropertiesMapNested() throws Exception { + public void testPropertiesListNested() throws Exception { Foo foo = new Foo(); Map<String, Object> prop = new LinkedHashMap<>(); @@ -85,10 +86,10 @@ public class PropertyBindingSupportMapTest extends ContextTestSupport { 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"); + prop.put("bar.works[0]", "#bean:company1"); + prop.put("bar.works[0].id", "666"); + prop.put("bar.works[1]", "#bean:company2"); + prop.put("bar.works[1].name", "I changed this"); PropertyBindingSupport.bindProperties(context, foo, prop); @@ -97,47 +98,47 @@ public class PropertyBindingSupportMapTest extends ContextTestSupport { 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()); + assertEquals(666, foo.getBar().getWorks().get(0).getId()); + assertEquals("Acme", foo.getBar().getWorks().get(0).getName()); + assertEquals(456, foo.getBar().getWorks().get(1).getId()); + assertEquals("I changed this", foo.getBar().getWorks().get(1).getName()); } @Test - public void testPropertiesMapFirst() throws Exception { + public void testPropertiesListFirst() throws Exception { Bar bar = new Bar(); Map<String, Object> prop = new LinkedHashMap<>(); - prop.put("works[acme]", "#bean:company1"); - prop.put("works[acme].id", "666"); - prop.put("works[burger]", "#bean:company2"); - prop.put("works[burger].name", "I changed this"); + prop.put("works[0]", "#bean:company1"); + prop.put("works[0].id", "666"); + prop.put("works[1]", "#bean:company2"); + prop.put("works[1].name", "I changed this"); PropertyBindingSupport.bindProperties(context, bar, prop); assertEquals(2, bar.getWorks().size()); - assertEquals(666, bar.getWorks().get("acme").getId()); - assertEquals("Acme", bar.getWorks().get("acme").getName()); - assertEquals(456, bar.getWorks().get("burger").getId()); - assertEquals("I changed this", bar.getWorks().get("burger").getName()); + assertEquals(666, bar.getWorks().get(0).getId()); + assertEquals("Acme", bar.getWorks().get(0).getName()); + assertEquals(456, bar.getWorks().get(1).getId()); + assertEquals("I changed this", bar.getWorks().get(1).getName()); } @Test - public void testPropertiesNotMap() throws Exception { + public void testPropertiesNotList() 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"); + prop.put("bar.gold-customer[]", "true"); try { PropertyBindingSupport.bindProperties(context, foo, prop); fail("Should have thrown exception"); } catch (PropertyBindingException e) { - assertEquals("bar.gold-customer[foo]", e.getPropertyName()); + assertEquals("bar.gold-customer[]", 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")); + assertTrue(iae.getMessage().startsWith("Cannot set property: gold-customer[] as either a Map/List because target bean is not a Map or List type")); } } @@ -165,7 +166,7 @@ public class PropertyBindingSupportMapTest extends ContextTestSupport { public static class Bar { private int age; private boolean rider; - private Map<String, Company> works; // should auto-create this via the setter + private List<Company> works; // should auto-create this via the setter private boolean goldCustomer; public int getAge() { @@ -184,11 +185,11 @@ public class PropertyBindingSupportMapTest extends ContextTestSupport { this.rider = rider; } - public Map<String, Company> getWorks() { + public List<Company> getWorks() { return works; } - public void setWorks(Map<String, Company> works) { + public void setWorks(List<Company> works) { this.works = works; } 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 index f46e976..0acaa24 100644 --- 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 @@ -137,7 +137,7 @@ public class PropertyBindingSupportMapTest extends ContextTestSupport { } 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")); + assertTrue(iae.getMessage().startsWith("Cannot set property: gold-customer[foo] as either a Map/List because target bean is not a Map or List type")); } } 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 700260a..05893a7 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 @@ -44,6 +44,8 @@ import org.apache.camel.util.StringHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.camel.util.ObjectHelper.isNotEmpty; + /** * Helper for introspections of beans. * <p/> @@ -538,16 +540,28 @@ public final class IntrospectionSupport { 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 + // does the property name include a lookup key, then we need to set the property as a map or list if (name.contains("[") && name.endsWith("]")) { int pos = name.indexOf('['); - String mapKey = name.substring(pos + 1, name.length() - 1); + String lookupKey = 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<>(); + // it was supposed to be a list or map, but its null, so lets create a new list or map and set it automatically + Method getter = IntrospectionSupport.getPropertyGetter(target.getClass(), key); + if (getter != null) { + // what type does it have + Class<?> returnType = getter.getReturnType(); + if (Map.class.isAssignableFrom(returnType)) { + obj = new LinkedHashMap<>(); + } else if (Collection.class.isAssignableFrom(returnType)) { + obj = new ArrayList<>(); + } + } else { + // fallback as map type + 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"); @@ -558,11 +572,23 @@ public final class IntrospectionSupport { if (context != null && refName != null && value == null) { value = CamelContextHelper.lookup(context, refName); } - map.put(mapKey, value); + map.put(lookupKey, value); + return true; + } else if (obj instanceof List) { + List list = (List) obj; + if (context != null && refName != null && value == null) { + value = CamelContextHelper.lookup(context, refName); + } + if (isNotEmpty(lookupKey)) { + int idx = Integer.valueOf(lookupKey); + list.add(idx, value); + } else { + list.add(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); + // not a map or list + throw new IllegalArgumentException("Cannot set property: " + name + " as either a Map/List because target bean is not a Map or List type: " + target); } } 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 760029c..9741b80 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 @@ -20,6 +20,7 @@ import java.lang.reflect.Method; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; @@ -29,6 +30,8 @@ import org.apache.camel.CamelContext; import org.apache.camel.PropertyBindingException; import static org.apache.camel.support.IntrospectionSupport.findSetterMethods; +import static org.apache.camel.util.ObjectHelper.isNotEmpty; +import static org.apache.camel.util.StringHelper.notEmpty; /** * A convenient support class for binding String valued properties to an instance which @@ -36,7 +39,9 @@ import static org.apache.camel.support.IntrospectionSupport.findSetterMethods; * <ul> * <li>property placeholders - Keys and values using Camels property placeholder will be resolved</li> * <li>nested - Properties can be nested using the dot syntax (OGNL and builder pattern using with as prefix), eg foo.bar=123</li> - * <li>keys with map</li> - Properties can lookup in Map's using map syntax, eg foo[bar] where foo is the name of the property that is a Map instance, and bar is the name of the key.</li> + * <li>map</li> - Properties can lookup in Map's using map syntax, eg foo[bar] where foo is the name of the property that is a Map instance, and bar is the name of the key.</li> + * <li>list</li> - Properties can refer or add to in List's using list syntax, eg foo[0] where foo is the name of the property that is a + * List instance, and 0 is the index. To refer to the last element, then use last as key.</li> * <li>reference by bean id - Values can refer to other beans in the registry by prefixing with #nean: eg #bean:myBean</li> * <li>reference by type - Values can refer to singleton beans by their type in the registry by prefixing with #type: syntax, eg #type:com.foo.MyClassType</li> * <li>autowire by type - Values can refer to singleton beans by auto wiring by setting the value to #autowired</li> @@ -444,22 +449,34 @@ public final class PropertyBindingSupport { private static Object getOrElseProperty(Object target, String property, Object defaultValue) { String key = property; - String mapKey = null; + String lookupKey = null; // support maps in keys if (property.contains("[") && property.endsWith("]")) { int pos = property.indexOf('['); - mapKey = property.substring(pos + 1, property.length() - 1); + lookupKey = 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) { + if (answer instanceof Map && lookupKey != null) { Map map = (Map) answer; - answer = map.getOrDefault(mapKey, defaultValue); + answer = map.getOrDefault(lookupKey, defaultValue); + } else if (answer instanceof List) { + List list = (List) answer; + if (isNotEmpty(lookupKey)) { + int idx = Integer.valueOf(lookupKey); + answer = list.get(idx); + } else { + if (list.isEmpty()) { + answer = null; + } else { + answer = list.get(list.size() - 1); + } + } } - return answer; + return answer != null ? answer : defaultValue; } private static Method findBestSetterMethod(Class clazz, String name, boolean fluentBuilder) {