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/05/30 12:44:18 UTC
[camel] 01/03: CAMEL-13557: Add property binding support to make it
convenient to configure components and whatnot.
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 <cl...@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);