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/24 10:12:41 UTC

[camel] 01/27: 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 f2e24c802005649520e6971b776caa22643b6a8a
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed May 22 15:47:57 2019 +0200

    CAMEL-13557: Add property binding support to make it convenient to configure components and whatnot.
---
 .../java/org/apache/camel/main/MainSupport.java    |  31 +----
 .../camel/support/PropertyBindingSupportTest.java  | 146 +++++++++++++++++++++
 .../apache/camel/support/IntrospectionSupport.java |  58 +++++++-
 .../camel/support/PropertyBindingSupport.java      |  47 +++++++
 4 files changed, 254 insertions(+), 28 deletions(-)

diff --git a/core/camel-core/src/main/java/org/apache/camel/main/MainSupport.java b/core/camel-core/src/main/java/org/apache/camel/main/MainSupport.java
index 0f3570e..fedb2cd 100644
--- a/core/camel-core/src/main/java/org/apache/camel/main/MainSupport.java
+++ b/core/camel-core/src/main/java/org/apache/camel/main/MainSupport.java
@@ -81,10 +81,9 @@ import org.apache.camel.spi.StreamCachingStrategy;
 import org.apache.camel.spi.ThreadPoolProfile;
 import org.apache.camel.spi.UnitOfWorkFactory;
 import org.apache.camel.spi.UuidGenerator;
-import org.apache.camel.support.DefaultExchange;
-import org.apache.camel.support.EndpointHelper;
 import org.apache.camel.support.IntrospectionSupport;
 import org.apache.camel.support.LifecycleStrategySupport;
+import org.apache.camel.support.PropertyBindingSupport;
 import org.apache.camel.support.jsse.GlobalSSLContextParametersSupplier;
 import org.apache.camel.support.service.ServiceHelper;
 import org.apache.camel.support.service.ServiceSupport;
@@ -1261,6 +1260,8 @@ public abstract class MainSupport extends ServiceSupport {
                 Map<String, Object> properties = new LinkedHashMap<>();
                 IntrospectionSupport.getProperties(component, properties, null);
 
+                // TODO: Use PropertyBindingSupport to make it support this kind of use-case too
+
                 // lookup complex types
                 properties.forEach((k, v) -> {
                     // if the property has not been set and its a complex type (not simple or string etc)
@@ -1343,34 +1344,10 @@ public abstract class MainSupport extends ServiceSupport {
             String name = entry.getKey();
             Object value = entry.getValue();
 
-            // if the name has dot's then its an OGNL expressions (so lets use simple language to walk down this ognl path)
-            boolean ognl = name.contains(".");
-            if (ognl) {
-                Language method = context.resolveLanguage("simple");
-                String path = name.substring(0, name.lastIndexOf('.'));
-                Expression exp = method.createExpression("${body." + path + "}");
-                Exchange dummy = new DefaultExchange(context);
-                dummy.getMessage().setBody(target);
-                Object newTarget = exp.evaluate(dummy, Object.class);
-                if (newTarget != null) {
-                    target = newTarget;
-                    name = name.substring(name.lastIndexOf('.') + 1);
-                }
-            }
-
             String stringValue = value != null ? value.toString() : null;
-            boolean hit = false;
 
             LOG.debug("Setting property {} on {} with value {}", name, target, stringValue);
-            if (EndpointHelper.isReferenceParameter(stringValue)) {
-                hit = IntrospectionSupport.setProperty(context, context.getTypeConverter(), target, name, null, stringValue, true);
-            } else if (value != null) {
-                try {
-                    hit = IntrospectionSupport.setProperty(context, context.getTypeConverter(), target, name, value);
-                } catch (IllegalArgumentException var12) {
-                    hit = IntrospectionSupport.setProperty(context, context.getTypeConverter(), target, name, null, stringValue, true);
-                }
-            }
+            boolean hit = PropertyBindingSupport.bindProperty(context, target, name, stringValue);
 
             if (hit) {
                 it.remove();
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
new file mode 100644
index 0000000..c55745f
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/support/PropertyBindingSupportTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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 org.apache.camel.CamelContext;
+import org.apache.camel.ContextTestSupport;
+import org.junit.Test;
+
+/**
+ * Unit test for PropertyBindingSupport with nested properties
+ */
+public class PropertyBindingSupportTest 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);
+
+        return context;
+    }
+
+    @Test
+    public void testNested() throws Exception {
+        Foo foo = new Foo();
+
+        PropertyBindingSupport.bindProperty(context, foo, "name", "James");
+        PropertyBindingSupport.bindProperty(context, foo, "bar.age", "33");
+        PropertyBindingSupport.bindProperty(context, foo, "bar.rider", "true");
+        PropertyBindingSupport.bindProperty(context, foo, "bar.work.id", "123");
+        PropertyBindingSupport.bindProperty(context, foo, "bar.work.name", "Acme");
+
+        assertEquals("James", foo.getName());
+        assertEquals(33, foo.getBar().getAge());
+        assertTrue(foo.getBar().isRider());
+        assertEquals(123, foo.getBar().getWork().getId());
+        assertEquals("Acme", foo.getBar().getWork().getName());
+    }
+
+    @Test
+    public void testNestedReference() throws Exception {
+        Foo foo = new Foo();
+
+        PropertyBindingSupport.bindProperty(context, foo, "name", "James");
+        PropertyBindingSupport.bindProperty(context, foo, "bar.age", "33");
+        PropertyBindingSupport.bindProperty(context, foo, "bar.rider", "true");
+        PropertyBindingSupport.bindProperty(context, foo, "bar.work", "#myWork");
+
+        assertEquals("James", foo.getName());
+        assertEquals(33, foo.getBar().getAge());
+        assertTrue(foo.getBar().isRider());
+        assertEquals(456, foo.getBar().getWork().getId());
+        assertEquals("Acme", foo.getBar().getWork().getName());
+    }
+
+    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 Company work; // has no default value but Camel can automatic create one if there is a setter
+
+        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 Company getWork() {
+            return work;
+        }
+
+        public void setWork(Company work) {
+            this.work = work;
+        }
+    }
+
+    public static class Company {
+        private int id;
+        private String name;
+
+        public int getId() {
+            return id;
+        }
+
+        public void setId(int id) {
+            this.id = id;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+    }
+
+}
+
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 55f2612..28a1e3f 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
@@ -513,12 +513,68 @@ public final class IntrospectionSupport {
      * 2. 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 {
+        return setProperty(context, typeConverter, target, name, value, refName, allowBuilderPattern, false);
+    }
+    /**
+     * This method supports two 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
+     * 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
+     * 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 {
+    public static boolean setProperty(CamelContext context, TypeConverter typeConverter, Object target, String name, Object value, String refName,
+                                      boolean allowBuilderPattern, boolean allowNestedProperties) throws Exception {
         Class<?> clazz = target.getClass();
         Collection<Method> setters;
 
+        // if name has dot then we need to OGNL walk it
+        if (allowNestedProperties && name.indexOf('.') > 0) {
+            String[] parts = name.split("\\.");
+            Object newTarget = target;
+            Class<?> newClass = clazz;
+            // we should only iterate until until 2nd last so we use -1 in the for loop
+            for (int i = 0; i < parts.length - 1; i++) {
+                String part = parts[i];
+                Object prop = getOrElseProperty(newTarget, part, null);
+                if (prop == null) {
+                    // okay is there a setter so we can create a new instance and set it automatic
+                    Set<Method> newSetters = findSetterMethods(newClass, part, true);
+                    if (newSetters.size() == 1) {
+                        Method method = newSetters.iterator().next();
+                        Class<?> parameterType = method.getParameterTypes()[0];
+                        if (parameterType != null && org.apache.camel.util.ObjectHelper.hasDefaultPublicNoArgConstructor(parameterType)) {
+                            Object instance = context.getInjector().newInstance(parameterType);
+                            if (instance != null) {
+                                org.apache.camel.support.ObjectHelper.invokeMethod(method, newTarget, instance);
+                                newTarget = instance;
+                                newClass = newTarget.getClass();
+                            }
+                        }
+                    }
+                } else {
+                    newTarget = prop;
+                    newClass = newTarget.getClass();
+                }
+            }
+            // okay we found a nested property, then lets change to use that
+            target = newTarget;
+            clazz = newTarget.getClass();
+            name = parts[parts.length - 1];
+            if (value instanceof String) {
+                if (EndpointHelper.isReferenceParameter(value.toString())) {
+                    // okay its a reference so swap to lookup this
+                    refName = value.toString();
+                    value = null;
+                }
+            }
+        }
+
         // we need to lookup the value from the registry
         if (context != null && refName != null && value == null) {
             setters = findSetterMethodsOrderedByParameterType(clazz, name, allowBuilderPattern);
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
new file mode 100644
index 0000000..b9d98bf
--- /dev/null
+++ b/core/camel-support/src/main/java/org/apache/camel/support/PropertyBindingSupport.java
@@ -0,0 +1,47 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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 org.apache.camel.CamelContext;
+
+import java.util.Map;
+
+/**
+ * A convenient support class for binding String valued properties to an instance which
+ * uses a set of conventions:
+ * <ul>
+ *     <li>nested - Properties can be nested using the dot syntax (OGNL)</li>
+ *     <li>reference by id - Values can refer to other beans by their id using # syntax</li>
+ * </ul>
+ */
+public final class PropertyBindingSupport {
+
+    private PropertyBindingSupport() {
+    }
+
+    public static boolean bindProperties(CamelContext camelContext, Object target, Map<String, Object> properties) throws Exception {
+        boolean answer = true;
+        for (Map.Entry<String, Object> entry : properties.entrySet()) {
+            answer &= bindProperty(camelContext, target, entry.getKey(), entry.getValue());
+        }
+        return answer;
+    }
+
+    public static boolean bindProperty(CamelContext camelContext, Object target, String name, Object value) throws Exception {
+        return IntrospectionSupport.setProperty(camelContext, camelContext.getTypeConverter(), target, name, value, null, true, true);
+    }
+}