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 2020/08/25 10:06:42 UTC

[camel] branch CAMEL-15437 created (now 581c07f)

This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a change to branch CAMEL-15437
in repository https://gitbox.apache.org/repos/asf/camel.git.


      at 581c07f  CAMEL-15437: properties-binding: support binding from maps of maps

This branch includes the following new commits:

     new 581c07f  CAMEL-15437: properties-binding: support binding from maps of maps

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[camel] 01/01: CAMEL-15437: properties-binding: support binding from maps of maps

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch CAMEL-15437
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 581c07f090370697943afc4a0f63b6ab31f3ff66
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Tue Aug 25 12:05:51 2020 +0200

    CAMEL-15437: properties-binding: support binding from maps of maps
---
 .../support/PropertyBindingSupportFlattenTest.java | 405 +++++++++++++++++++++
 .../camel/support/PropertyBindingSupportTest.java  |   1 -
 .../camel/support/PropertyBindingSupport.java      | 105 +++++-
 3 files changed, 505 insertions(+), 6 deletions(-)

diff --git a/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportFlattenTest.java b/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportFlattenTest.java
new file mode 100644
index 0000000..6450e13
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportFlattenTest.java
@@ -0,0 +1,405 @@
+/*
+ * 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.HashMap;
+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.apache.camel.spi.PropertiesComponent;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Unit test for PropertyBindingSupport
+ */
+public class PropertyBindingSupportFlattenTest 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);
+
+        Properties placeholders = new Properties();
+        placeholders.put("companyName", "Acme");
+        placeholders.put("committer", "rider");
+        context.getPropertiesComponent().setInitialProperties(placeholders);
+
+        return context;
+    }
+
+    @Test
+    public void testProperties() throws Exception {
+        Foo foo = new Foo();
+
+        // Map.of requires JDK9 onwards and we are still compatible with Java 8
+        Map<String, Object> work = new HashMap<>();
+        work.put("id", "123");
+        work.put("name", "{{companyName}}");
+
+        Map<String, Object> bar = new HashMap<>();
+        bar.put("age", "33");
+        bar.put("{{committer}}", "true");
+        bar.put("gold-customer", "true");
+        bar.put("work", work);
+
+        Map<String, Object> map = new HashMap<>();
+        map.put("name", "James");
+        map.put("bar", bar);
+
+        PropertyBindingSupport.bindWithFlattenProperties(context, foo, map);
+
+        assertEquals("James", foo.getName());
+        assertEquals(33, foo.getBar().getAge());
+        assertTrue(foo.getBar().isRider());
+        assertTrue(foo.getBar().isGoldCustomer());
+        assertEquals(123, foo.getBar().getWork().getId());
+        assertEquals("Acme", foo.getBar().getWork().getName());
+
+        assertTrue(map.isEmpty(), "Should bind all properties");
+    }
+
+    @Test
+    public void testProperty() throws Exception {
+        PropertiesComponent pc = context.getPropertiesComponent();
+        Properties prop = new Properties();
+        prop.setProperty("customerName", "James");
+        prop.setProperty("customerAge", "33");
+        prop.setProperty("workKey", "customerWork");
+        prop.setProperty("customerWork", "Acme");
+        pc.setInitialProperties(prop);
+
+        Foo foo = new Foo();
+
+        // Map.of requires JDK9 onwards and we are still compatible with Java 8
+        Map<String, Object> work = new HashMap<>();
+        work.put("id", "456");
+        work.put("name", "#property:{{workKey}}");
+
+        Map<String, Object> bar = new HashMap<>();
+        bar.put("age", "#property:customerAge");
+        bar.put("rider", "true");
+        bar.put("gold-customer", "true");
+        bar.put("work", work);
+
+        Map<String, Object> map = new HashMap<>();
+        map.put("name", "#property:customerName");
+        map.put("bar", bar);
+
+        PropertyBindingSupport.bindWithFlattenProperties(context, foo, map);
+
+        assertEquals("James", foo.getName());
+        assertEquals(33, foo.getBar().getAge());
+        assertTrue(foo.getBar().isRider());
+        assertTrue(foo.getBar().isGoldCustomer());
+        assertEquals(456, foo.getBar().getWork().getId());
+        assertEquals("Acme", foo.getBar().getWork().getName());
+
+        try {
+            PropertyBindingSupport.build().bind(context, foo, "name", "#property:unknown");
+            fail("Should have thrown exception");
+        } catch (PropertyBindingException e) {
+            assertEquals("name", e.getPropertyName());
+            assertEquals("#property:unknown", e.getValue());
+            assertEquals("Property with key unknown not found by properties component", e.getCause().getMessage());
+        }
+    }
+
+    @Test
+    public void testWithFluentBuilder() throws Exception {
+        Foo foo = new Foo();
+
+        // Map.of requires JDK9 onwards and we are still compatible with Java 8
+        Map<String, Object> work = new HashMap<>();
+        work.put("name", "{{companyName}}");
+
+        Map<String, Object> bar = new HashMap<>();
+        bar.put("age", "33");
+        bar.put("rider", "true");
+        bar.put("gold-customer", "true");
+        bar.put("work", work);
+
+        Map<String, Object> prop = new HashMap<>();
+        prop.put("bar", bar);
+
+        PropertyBindingSupport.build().withCamelContext(context).withTarget(foo).withProperty("name", "James")
+                .withProperty("bar.work.id", "123")
+                .withFlattenProperties(true)
+                // and add the rest
+                .withProperties(prop).bind();
+
+        assertEquals("James", foo.getName());
+        assertEquals(33, foo.getBar().getAge());
+        assertTrue(foo.getBar().isRider());
+        assertTrue(foo.getBar().isGoldCustomer());
+        assertEquals(123, foo.getBar().getWork().getId());
+        assertEquals("Acme", foo.getBar().getWork().getName());
+
+        assertTrue(prop.isEmpty(), "Should bind all properties");
+    }
+
+    @Test
+    public void testPropertiesNoReflection() throws Exception {
+        Foo foo = new Foo();
+
+        // Map.of requires JDK9 onwards and we are still compatible with Java 8
+        Map<String, Object> bar = new HashMap<>();
+        bar.put("AGE", "33");
+
+        Map<String, Object> prop = new HashMap<>();
+        prop.put("bar", bar);
+        prop.put("name", "James");
+
+        PropertyBindingSupport.build().withFlattenProperties(true).withReflection(false).bind(context, foo, prop);
+
+        assertNull(foo.getName());
+        assertEquals(0, foo.getBar().getAge());
+
+        // should not bind any properties as reflection is off
+        assertEquals(2, prop.size());
+    }
+
+    @Test
+    public void testPropertiesIgnoreCase() throws Exception {
+        Foo foo = new Foo();
+
+        // Map.of requires JDK9 onwards and we are still compatible with Java 8
+        Map<String, Object> work = new HashMap<>();
+        work.put("naME", "{{companyName}}");
+        work.put("ID", "123");
+
+        Map<String, Object> bar = new HashMap<>();
+        bar.put("AGE", "33");
+        bar.put("{{committer}}", "true");
+        bar.put("gOLd-Customer", "true");
+        bar.put("WoRk", work);
+
+        Map<String, Object> prop = new HashMap<>();
+        prop.put("bar", bar);
+        prop.put("name", "James");
+
+        PropertyBindingSupport.build().withFlattenProperties(true).withIgnoreCase(true).bind(context, foo, prop);
+
+        assertEquals("James", foo.getName());
+        assertEquals(33, foo.getBar().getAge());
+        assertTrue(foo.getBar().isRider());
+        assertTrue(foo.getBar().isGoldCustomer());
+        assertEquals(123, foo.getBar().getWork().getId());
+        assertEquals("Acme", foo.getBar().getWork().getName());
+
+        assertTrue(prop.isEmpty(), "Should bind all properties");
+    }
+
+    @Test
+    public void testPropertiesDash() throws Exception {
+        Foo foo = new Foo();
+
+        // Map.of requires JDK9 onwards and we are still compatible with Java 8
+        Map<String, Object> work = new HashMap<>();
+        work.put("name", "{{companyName}}");
+        work.put("id", "123");
+
+        Map<String, Object> bar = new HashMap<>();
+        bar.put("age", "33");
+        bar.put("{{committer}}", "true");
+        bar.put("gold-customer", "true");
+        bar.put("work", work);
+
+        Map<String, Object> prop = new HashMap<>();
+        prop.put("bar", bar);
+        prop.put("name", "James");
+
+        PropertyBindingSupport.build().withFlattenProperties(true).bind(context, foo, prop);
+
+        assertEquals("James", foo.getName());
+        assertEquals(33, foo.getBar().getAge());
+        assertTrue(foo.getBar().isRider());
+        assertTrue(foo.getBar().isGoldCustomer());
+        assertEquals(123, foo.getBar().getWork().getId());
+        assertEquals("Acme", foo.getBar().getWork().getName());
+
+        assertTrue(prop.isEmpty(), "Should bind all properties");
+    }
+
+    @Test
+    public void testBindPropertiesWithOptionPrefix() throws Exception {
+        Foo foo = new Foo();
+
+        // Map.of requires JDK9 onwards and we are still compatible with Java 8
+        Map<String, Object> work = new HashMap<>();
+        work.put("name", "{{companyName}}");
+        work.put("id", "123");
+
+        Map<String, Object> bar = new HashMap<>();
+        bar.put("age", "33");
+        bar.put("{{committer}}", "true");
+        bar.put("gold-customer", "true");
+        bar.put("work", work);
+
+        Map<String, Object> prop = new HashMap<>();
+        prop.put("bar", bar);
+        prop.put("name", "James");
+
+        Map<String, Object> other = new HashMap<>();
+        other.put("something", "test");
+
+        Map<String, Object> root = new HashMap<>();
+        root.put("prefix", prop);
+        root.put("other", other);
+
+        Map<String, Object> my = new HashMap<>();
+        my.put("my", root);
+
+        PropertyBindingSupport.build().withFlattenProperties(true).withOptionPrefix("my.prefix.").bind(context, foo, my);
+
+        assertEquals("James", foo.getName());
+        assertEquals(33, foo.getBar().getAge());
+        assertTrue(foo.getBar().isRider());
+        assertTrue(foo.getBar().isGoldCustomer());
+        assertEquals(123, foo.getBar().getWork().getId());
+        assertEquals("Acme", foo.getBar().getWork().getName());
+        assertEquals("test", other.get("something"));
+        assertEquals(1, other.size());
+    }
+
+    @Test
+    public void testPropertiesOptionalKey() throws Exception {
+        Foo foo = new Foo();
+
+        // Map.of requires JDK9 onwards and we are still compatible with Java 8
+        Map<String, Object> adr = new HashMap<>();
+        adr.put("?addresss", "Some street");
+        adr.put("?zip", "1234");
+
+        Map<String, Object> work = new HashMap<>();
+        work.put("?naME", "{{companyName}}");
+        work.put("?ID", "123");
+        work.put("address", adr);
+
+        Map<String, Object> bar = new HashMap<>();
+        bar.put("AGE", "33");
+        bar.put("{{committer}}", "true");
+        bar.put("gOLd-Customer", "true");
+        bar.put("?silver-Customer", "true");
+        bar.put("WoRk", work);
+
+        Map<String, Object> prop = new HashMap<>();
+        prop.put("bar", bar);
+        prop.put("name", "James");
+
+        PropertyBindingSupport.build().withFlattenProperties(true).withIgnoreCase(true).bind(context, foo, prop);
+
+        assertEquals("James", foo.getName());
+        assertEquals(33, foo.getBar().getAge());
+        assertTrue(foo.getBar().isRider());
+        assertTrue(foo.getBar().isGoldCustomer());
+        assertEquals(123, foo.getBar().getWork().getId());
+        assertEquals("Acme", foo.getBar().getWork().getName());
+
+        assertFalse(prop.isEmpty(), "Should NOT bind all properties");
+        assertTrue(bar.containsKey("?silver-Customer"));
+        assertTrue(adr.containsKey("?addresss"));
+        assertTrue(adr.containsKey("?zip"));
+    }
+
+    public static class Foo {
+        private String name;
+        private Bar bar = new Bar();
+        private Animal animal;
+
+        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 Animal getAnimal() {
+            return animal;
+        }
+
+        public void setAnimal(Animal animal) {
+            this.animal = animal;
+        }
+    }
+
+    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
+        private boolean goldCustomer;
+
+        public int getAge() {
+            return age;
+        }
+
+        public boolean isRider() {
+            return rider;
+        }
+
+        public Company getWork() {
+            return work;
+        }
+
+        public boolean isGoldCustomer() {
+            return goldCustomer;
+        }
+
+        // this has no setter but only builders
+        // and mix the builders with both styles (with as prefix and no prefix
+        // at all)
+
+        public Bar withAge(int age) {
+            this.age = age;
+            return this;
+        }
+
+        public Bar withRider(boolean rider) {
+            this.rider = rider;
+            return this;
+        }
+
+        public Bar work(Company work) {
+            this.work = work;
+            return this;
+        }
+
+        public Bar goldCustomer(boolean goldCustomer) {
+            this.goldCustomer = goldCustomer;
+            return this;
+        }
+    }
+
+}
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
index d9371b0..e1e34e5 100644
--- 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
@@ -577,7 +577,6 @@ public class PropertyBindingSupportTest extends ContextTestSupport {
             PropertyBindingSupport.build().withIgnoreCase(true).withMandatory(true).bind(context, foo, prop);
             fail("Should fail");
         } catch (PropertyBindingException e) {
-            //            assertEquals("bar.unknown", e.getPropertyName());
             assertEquals("unknown", e.getPropertyName());
         }
     }
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 246d339..2332f1e 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
@@ -97,6 +97,7 @@ public final class PropertyBindingSupport {
         private Object target;
         private Map<String, Object> properties;
         private boolean removeParameters = true;
+        private boolean flattenProperties;
         private boolean mandatory;
         private boolean nesting = true;
         private boolean deepNesting = true;
@@ -160,6 +161,14 @@ public final class PropertyBindingSupport {
         }
 
         /**
+         * Whether properties should be flattened (when properties is a map of maps).
+         */
+        public Builder withFlattenProperties(boolean flattenProperties) {
+            this.flattenProperties = flattenProperties;
+            return this;
+        }
+
+        /**
          * Whether all parameters should be mandatory and successfully bound
          */
         public Builder withMandatory(boolean mandatory) {
@@ -265,7 +274,7 @@ public final class PropertyBindingSupport {
             }
 
             return doBindProperties(camelContext, target, removeParameters ? properties : new HashMap<>(properties),
-                    optionPrefix, ignoreCase, true, mandatory,
+                    optionPrefix, ignoreCase, removeParameters, flattenProperties, mandatory,
                     nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer);
         }
 
@@ -283,7 +292,7 @@ public final class PropertyBindingSupport {
             Map<String, Object> prop = properties != null ? properties : this.properties;
 
             return doBindProperties(context, obj, removeParameters ? prop : new HashMap<>(prop),
-                    optionPrefix, ignoreCase, true, mandatory,
+                    optionPrefix, ignoreCase, removeParameters, flattenProperties, mandatory,
                     nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer);
         }
 
@@ -300,7 +309,7 @@ public final class PropertyBindingSupport {
             Map<String, Object> properties = new HashMap<>(1);
             properties.put(key, value);
 
-            return doBindProperties(camelContext, target, properties, optionPrefix, ignoreCase, true, mandatory,
+            return doBindProperties(camelContext, target, properties, optionPrefix, ignoreCase, true, false, mandatory,
                     nesting, deepNesting, fluentBuilder, allowPrivateSetter, reference, placeholder, reflection, configurer);
         }
 
@@ -524,7 +533,7 @@ public final class PropertyBindingSupport {
      *
      * @param  camelContext the camel context
      * @param  target       the target object
-     * @param  properties   the properties where the bound properties will be removed from
+     * @param  properties   the properties (as flat key=value paris) where the bound properties will be removed
      * @return              true if one or more properties was bound
      * @see                 #build()
      */
@@ -538,6 +547,29 @@ public final class PropertyBindingSupport {
     }
 
     /**
+     * Binds the properties to the target object, and removes the property that was bound from properties.
+     * <p/>
+     * This method uses the default settings, and if you need to configure any setting then use the fluent builder
+     * {@link #build()} where each option can be customized, such as whether parameter should be removed, or whether
+     * options are mandatory etc.
+     *
+     * @param  camelContext the camel context
+     * @param  target       the target object
+     * @param  properties   the properties as (map of maps) where the properties will be flattened, and bound properties
+     *                      will be removed
+     * @return              true if one or more properties was bound
+     * @see                 #build()
+     */
+    public static boolean bindWithFlattenProperties(CamelContext camelContext, Object target, Map<String, Object> properties) {
+        // mandatory parameters
+        org.apache.camel.util.ObjectHelper.notNull(camelContext, "camelContext");
+        org.apache.camel.util.ObjectHelper.notNull(target, "target");
+        org.apache.camel.util.ObjectHelper.notNull(properties, "properties");
+
+        return PropertyBindingSupport.build().withFlattenProperties(true).bind(camelContext, target, properties);
+    }
+
+    /**
      * Used for making it easier to support using option prefix in property binding and to remove the bound properties
      * from the input map.
      */
@@ -579,6 +611,64 @@ public final class PropertyBindingSupport {
     }
 
     /**
+     * Used for flatten properties when they are a map of maps
+     */
+    private static class FlattenMap extends LinkedHashMap<String, Object> {
+
+        private final Map<String, Object> originalMap;
+
+        public FlattenMap(Map<String, Object> map) {
+            this.originalMap = map;
+            flatten("", originalMap);
+        }
+
+        @SuppressWarnings("unchecked")
+        private void flatten(String prefix, Map<?, Object> map) {
+            for (Map.Entry<?, Object> entry : map.entrySet()) {
+                String key = entry.getKey().toString();
+                boolean optional = key.startsWith("?");
+                if (optional) {
+                    key = key.substring(1);
+                }
+                Object value = entry.getValue();
+                String keyPrefix = (optional ? "?" : "") + (prefix.isEmpty() ? key : prefix + "." + key);
+                if (value instanceof Map) {
+                    flatten(keyPrefix, (Map<?, Object>) value);
+                } else {
+                    put(keyPrefix, value);
+                }
+            }
+        }
+
+        @Override
+        public Object remove(Object key) {
+            // we only need to care about the remove method,
+            // so we can remove the corresponding key from the original map
+
+            // walk key with dots to remove right node
+            String[] parts = key.toString().split("\\.");
+            Map map = originalMap;
+            for (int i = 0; i < parts.length; i++) {
+                String part = parts[i];
+                Object obj = map.get(part);
+                if (i == parts.length - 1) {
+                    map.remove(part);
+                } else if (obj instanceof Map) {
+                    map = (Map) obj;
+                }
+            }
+
+            // remove empty middle maps
+            Object answer = super.remove(key);
+            if (super.isEmpty()) {
+                originalMap.clear();
+            }
+            return answer;
+        }
+
+    }
+
+    /**
      * Used for sorting the property keys when doing property binding. We need to sort the keys in a specific order so
      * we process the binding in a way that allows us to walk down the OGNL object graph and build empty nodes on the
      * fly, and as well handle map/list and array types as well.
@@ -622,6 +712,7 @@ public final class PropertyBindingSupport {
      * @param  optionPrefix       the prefix used to filter properties
      * @param  ignoreCase         whether to ignore case for property keys
      * @param  removeParameter    whether to remove bound parameters
+     * @param  flattenProperties  whether properties should be flattened (when properties is a map of maps)
      * @param  mandatory          whether all parameters must be bound
      * @param  nesting            whether nesting is in use
      * @param  deepNesting        whether deep nesting is in use, where Camel will attempt to walk as deep as possible
@@ -638,7 +729,7 @@ public final class PropertyBindingSupport {
      */
     private static boolean doBindProperties(
             CamelContext camelContext, Object target, Map<String, Object> properties,
-            String optionPrefix, boolean ignoreCase, boolean removeParameter, boolean mandatory,
+            String optionPrefix, boolean ignoreCase, boolean removeParameter, boolean flattenProperties, boolean mandatory,
             boolean nesting, boolean deepNesting, boolean fluentBuilder, boolean allowPrivateSetter,
             boolean reference, boolean placeholder,
             boolean reflection, PropertyConfigurer configurer) {
@@ -647,6 +738,10 @@ public final class PropertyBindingSupport {
             return false;
         }
 
+        if (flattenProperties) {
+            properties = new FlattenMap(properties);
+        }
+
         boolean answer = false;
 
         if (optionPrefix != null) {