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 2021/06/25 07:22:33 UTC

[camel] branch main updated: CAMEL-16759: camel-core - Kamelet add support for factory method in #class local bean

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

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


The following commit(s) were added to refs/heads/main by this push:
     new fa51da1  CAMEL-16759: camel-core - Kamelet add support for factory method in #class local bean
fa51da1 is described below

commit fa51da1029effa00b378293154a93d297c6c88af
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Fri Jun 25 09:21:24 2021 +0200

    CAMEL-16759: camel-core - Kamelet add support for factory method in #class local bean
---
 .../java/org/apache/camel/impl/DefaultModel.java   | 69 ++++++++++++++++++---
 .../camel/builder/MyConstructorProcessor.java      | 37 ++++++++++++
 .../camel/builder/RouteTemplateLocalBeanTest.java  | 70 ++++++++++++++++++++++
 .../camel/support/PropertyBindingSupport.java      | 24 +++++++-
 4 files changed, 189 insertions(+), 11 deletions(-)

diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java
index 453a205..5802e61 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultModel.java
@@ -70,6 +70,7 @@ import org.apache.camel.support.ScriptHelper;
 import org.apache.camel.support.service.ServiceHelper;
 import org.apache.camel.util.AntPathMatcher;
 import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.function.Suppliers;
 
 public class DefaultModel implements Model {
@@ -425,19 +426,69 @@ public class DefaultModel implements Model {
                     }));
                 }
             } else if (b.getBeanClass() != null || b.getType() != null && b.getType().startsWith("#class:")) {
-                Class<?> clazz = b.getBeanClass() != null
-                        ? b.getBeanClass() : camelContext.getClassResolver().resolveMandatoryClass(b.getType().substring(7));
-                // we only have the bean class so we use that to create a new bean via the injector
-                // and memorize so the bean is only created once and the local bean is the same
-                // if a route template refers to the local bean multiple times
-                routeTemplateContext.bind(b.getName(), clazz,
-                        Suppliers.memorize(() -> {
-                            Object local = camelContext.getInjector().newInstance(clazz);
+                // if there is a factory method then the class/bean should be created in a different way
+                String className = null;
+                String factoryMethod = null;
+                String parameters = null;
+                if (b.getType() != null) {
+                    className = b.getType().substring(7);
+                    if (className.endsWith(")") && className.indexOf('(') != -1) {
+                        parameters = StringHelper.after(className, "(");
+                        parameters = parameters.substring(0, parameters.length() - 1); // clip last )
+                        className = StringHelper.before(className, "(");
+                    }
+                    if (className != null && className.indexOf('#') != -1) {
+                        factoryMethod = StringHelper.after(className, "#");
+                        className = StringHelper.before(className, "#");
+                    }
+                }
+                if (className != null && (factoryMethod != null || parameters != null)) {
+                    final Class<?> clazz = camelContext.getClassResolver().resolveMandatoryClass(className);
+                    final String fqn = className;
+                    final String fm = factoryMethod;
+                    final String fp = parameters;
+                    routeTemplateContext.bind(b.getName(), Object.class, Suppliers.memorize(() -> {
+                        try {
+                            Object local;
+                            if (fm != null) {
+                                if (fp != null) {
+                                    // special to support factory method parameters
+                                    local = PropertyBindingSupport.newInstanceFactoryParameters(camelContext, clazz, fm, fp);
+                                } else {
+                                    local = camelContext.getInjector().newInstance(clazz, fm);
+                                }
+                                if (local == null) {
+                                    throw new IllegalStateException(
+                                            "Cannot create bean instance using factory method: " + fqn + "#" + fm);
+                                }
+                            } else {
+                                // special to support constructor parameters
+                                local = PropertyBindingSupport.newInstanceConstructorParameters(camelContext, clazz, fp);
+                            }
                             if (!props.isEmpty()) {
                                 setPropertiesOnTarget(camelContext, local, props);
                             }
                             return local;
-                        }));
+                        } catch (Exception e) {
+                            throw new IllegalStateException(
+                                    "Cannot create bean: " + b.getType());
+                        }
+                    }));
+                } else {
+                    Class<?> clazz = b.getBeanClass() != null
+                            ? b.getBeanClass() : camelContext.getClassResolver().resolveMandatoryClass(className);
+                    // we only have the bean class so we use that to create a new bean via the injector
+                    // and memorize so the bean is only created once and the local bean is the same
+                    // if a route template refers to the local bean multiple times
+                    routeTemplateContext.bind(b.getName(), clazz,
+                            Suppliers.memorize(() -> {
+                                Object local = camelContext.getInjector().newInstance(clazz);
+                                if (!props.isEmpty()) {
+                                    setPropertiesOnTarget(camelContext, local, props);
+                                }
+                                return local;
+                            }));
+                }
             } else if (b.getType() != null && b.getType().startsWith("#type:")) {
                 Class<?> clazz = camelContext.getClassResolver().resolveMandatoryClass(b.getType().substring(6));
                 Set<?> found = getCamelContext().getRegistry().findByType(clazz);
diff --git a/core/camel-core/src/test/java/org/apache/camel/builder/MyConstructorProcessor.java b/core/camel-core/src/test/java/org/apache/camel/builder/MyConstructorProcessor.java
new file mode 100644
index 0000000..ae77098
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/builder/MyConstructorProcessor.java
@@ -0,0 +1,37 @@
+/*
+ * 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.builder;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+
+public class MyConstructorProcessor implements Processor {
+
+    private String prefix = "";
+
+    public MyConstructorProcessor() {
+    }
+
+    public MyConstructorProcessor(String prefix) {
+        this.prefix = prefix;
+    }
+
+    @Override
+    public void process(Exchange exchange) throws Exception {
+        exchange.getMessage().setBody(prefix + exchange.getMessage().getBody());
+    }
+}
diff --git a/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateLocalBeanTest.java b/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateLocalBeanTest.java
index 7228b50..3780e38 100644
--- a/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateLocalBeanTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateLocalBeanTest.java
@@ -692,6 +692,70 @@ public class RouteTemplateLocalBeanTest extends ContextTestSupport {
         context.stop();
     }
 
+    @Test
+    public void testLocalBeanFactoryMethod() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                routeTemplate("myTemplate").templateParameter("foo").templateParameter("bar")
+                        .templateBean("myBar")
+                        .type("#class:org.apache.camel.builder.RouteTemplateLocalBeanTest#createBuilderProcessorThree('MyPrefix ')")
+                        .end()
+                        .from("direct:{{foo}}")
+                        .to("bean:{{bar}}");
+            }
+        });
+
+        context.start();
+
+        TemplatedRouteBuilder.builder(context, "myTemplate")
+                .parameter("foo", "one")
+                .parameter("bar", "myBar")
+                .routeId("myRoute")
+                .add();
+
+        assertEquals(1, context.getRoutes().size());
+
+        Object out = template.requestBody("direct:one", "World");
+        assertEquals("MyPrefix Builder3 World", out);
+
+        // should not be a global bean
+        assertNull(context.getRegistry().lookupByName("myBar"));
+
+        context.stop();
+    }
+
+    @Test
+    public void testLocalBeanConstructorParameter() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                routeTemplate("myTemplate").templateParameter("foo").templateParameter("bar")
+                        .templateBean("myBar").type("#class:org.apache.camel.builder.MyConstructorProcessor('MyCtr ')").end()
+                        .from("direct:{{foo}}")
+                        .to("bean:{{bar}}");
+            }
+        });
+
+        context.start();
+
+        TemplatedRouteBuilder.builder(context, "myTemplate")
+                .parameter("foo", "one")
+                .parameter("bar", "myBar")
+                .routeId("myRoute")
+                .add();
+
+        assertEquals(1, context.getRoutes().size());
+
+        Object out = template.requestBody("direct:one", "World");
+        assertEquals("MyCtr World", out);
+
+        // should not be a global bean
+        assertNull(context.getRegistry().lookupByName("myBar"));
+
+        context.stop();
+    }
+
     public static class BuilderTwoProcessor implements Processor {
 
         private String prefix = "";
@@ -739,4 +803,10 @@ public class RouteTemplateLocalBeanTest extends ContextTestSupport {
         return new BuilderTwoProcessor(rtc.getProperty("greeting", String.class));
     }
 
+    public static Processor createBuilderProcessorThree(String prefix) {
+        BuilderThreeProcessor answer = new BuilderThreeProcessor();
+        answer.setPrefix(prefix);
+        return answer;
+    }
+
 }
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 19fe59e..e72531e 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
@@ -1207,7 +1207,17 @@ public final class PropertyBindingSupport {
         return true;
     }
 
-    private static Object newInstanceConstructorParameters(CamelContext camelContext, Class<?> type, String parameters)
+    /**
+     * Creates a new bean instance using the constructor that takes the given set of parameters.
+     *
+     * @param  camelContext the camel context
+     * @param  type         the class type of the bean to create
+     * @param  parameters   the parameters for the constructor
+     * @return              the created bean, or null if there was no constructor that matched the given set of
+     *                      parameters
+     * @throws Exception    is thrown if error creating the bean
+     */
+    public static Object newInstanceConstructorParameters(CamelContext camelContext, Class<?> type, String parameters)
             throws Exception {
         String[] params = StringQuoteHelper.splitSafeQuote(parameters, ',');
         Constructor found = findMatchingConstructor(type.getConstructors(), params);
@@ -1281,7 +1291,17 @@ public final class PropertyBindingSupport {
         return candidates.size() == 1 ? candidates.get(0) : fallbackCandidate;
     }
 
-    private static Object newInstanceFactoryParameters(
+    /**
+     * Creates a new bean instance using a public static factory method from the given class
+     *
+     * @param  camelContext the camel context
+     * @param  type         the class with the public static factory method
+     * @param  parameters   optional parameters for the factory method
+     * @return              the created bean, or null if there was no factory method (optionally matched the given set
+     *                      of parameters)
+     * @throws Exception    is thrown if error creating the bean
+     */
+    public static Object newInstanceFactoryParameters(
             CamelContext camelContext, Class<?> type, String factoryMethod, String parameters)
             throws Exception {
         String[] params = StringQuoteHelper.splitSafeQuote(parameters, ',');