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);