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 e62ec25  CAMEL-16590: Kamelet local bean - Bind bean as templateBean 
to make it part of the spec
e62ec25 is described below

commit e62ec2529eff6dcf3f22034b401fc9b00939490c
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri May 7 12:04:20 2021 +0200

    CAMEL-16590: Kamelet local bean - Bind bean as templateBean to make it part 
of the spec
---
 ...est.java => KameletLocalBeanConfigureTest.java} |   2 +-
 ...lBeanTest.java => KameletLocalBeanIoCTest.java} |  20 +--
 ...Test.java => KameletLocalBeanSupplierTest.java} |  52 +++----
 .../component/kamelet/KameletLocalBeanTest.java    |   5 +-
 .../camel/component/kamelet/MyInjectBar.java       |  29 ++++
 .../org/apache/camel/RouteTemplateContext.java     |  13 +-
 .../java/org/apache/camel/impl/DefaultModel.java   |  23 +++
 .../services/org/apache/camel/model.properties     |   1 +
 .../resources/org/apache/camel/model/jaxb.index    |   1 +
 .../org/apache/camel/model/routeTemplate.json      |   1 +
 .../org/apache/camel/model/templateBean.json       |  17 +++
 .../camel/builder/TemplatedRouteBuilder.java       |   6 +-
 .../camel/model/DefaultRouteTemplateContext.java   |  17 ++-
 .../camel/model/RouteTemplateBeanDefinition.java   |  94 ++++++++++++
 .../camel/model/RouteTemplateDefinition.java       | 103 ++++++++++++-
 .../camel/builder/RouteTemplateLocalBeanTest.java  | 167 ++++++++++++++++++++-
 .../java/org/apache/camel/xml/in/ModelParser.java  |  11 ++
 .../modules/ROOT/pages/route-template.adoc         |  50 ++++--
 18 files changed, 528 insertions(+), 84 deletions(-)

diff --git 
a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTest.java
 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanConfigureTest.java
similarity index 97%
copy from 
components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTest.java
copy to 
components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanConfigureTest.java
index 3e72efd..ed0d025 100644
--- 
a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTest.java
+++ 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanConfigureTest.java
@@ -22,7 +22,7 @@ import org.apache.camel.test.junit5.CamelTestSupport;
 import org.apache.http.annotation.Obsolete;
 import org.junit.jupiter.api.Test;
 
-public class KameletLocalBeanTest extends CamelTestSupport {
+public class KameletLocalBeanConfigureTest extends CamelTestSupport {
 
     @Test
     public void testOne() throws Exception {
diff --git 
a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTest.java
 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanIoCTest.java
similarity index 80%
copy from 
components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTest.java
copy to 
components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanIoCTest.java
index 3e72efd..5338f02 100644
--- 
a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTest.java
+++ 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanIoCTest.java
@@ -22,7 +22,7 @@ import org.apache.camel.test.junit5.CamelTestSupport;
 import org.apache.http.annotation.Obsolete;
 import org.junit.jupiter.api.Test;
 
-public class KameletLocalBeanTest extends CamelTestSupport {
+public class KameletLocalBeanIoCTest extends CamelTestSupport {
 
     @Test
     public void testOne() throws Exception {
@@ -57,10 +57,8 @@ public class KameletLocalBeanTest extends CamelTestSupport {
             public void configure() throws Exception {
                 routeTemplate("whereTo")
                         .templateParameter("bar") // name of bar
-                        .configure(rtc -> {
-                            // create local bean with id myBar which can be 
used in the routes via {{myBar}}
-                            rtc.bind("myBar", MyBar.class, new 
MyBar(rtc.getProperty("bar", String.class)));
-                        })
+                        // use dependency injection to create the local bean 
via its fqn class name
+                        .templateBean("myBar", MyInjectBar.class)
                         .from("kamelet:source")
                         // must use {{myBar}} to refer to the local bean
                         .to("bean:{{myBar}}");
@@ -76,16 +74,4 @@ public class KameletLocalBeanTest extends CamelTestSupport {
         };
     }
 
-    private static class MyBar {
-
-        private final String bar;
-
-        public MyBar(String bar) {
-            this.bar = bar;
-        }
-
-        public String where(String name) {
-            return "Hi " + name + " we are going to " + bar;
-        }
-    }
 }
diff --git 
a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTest.java
 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanSupplierTest.java
similarity index 59%
copy from 
components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTest.java
copy to 
components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanSupplierTest.java
index 3e72efd..b141577 100644
--- 
a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTest.java
+++ 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanSupplierTest.java
@@ -16,30 +16,24 @@
  */
 package org.apache.camel.component.kamelet;
 
+import java.util.concurrent.atomic.AtomicInteger;
+
 import org.apache.camel.RoutesBuilder;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.test.junit5.CamelTestSupport;
 import org.apache.http.annotation.Obsolete;
 import org.junit.jupiter.api.Test;
 
-public class KameletLocalBeanTest extends CamelTestSupport {
-
-    @Test
-    public void testOne() throws Exception {
-        getMockEndpoint("mock:result").expectedBodiesReceived("Hi John we are 
going to Moes");
-
-        template.sendBody("direct:moe", "John");
+public class KameletLocalBeanSupplierTest extends CamelTestSupport {
 
-        assertMockEndpointsSatisfied();
-    }
+    private final AtomicInteger counter = new AtomicInteger();
 
     @Test
-    public void testTwo() throws Exception {
-        getMockEndpoint("mock:result").expectedBodiesReceived("Hi Jack we are 
going to Shamrock",
-                "Hi Mary we are going to Moes");
+    public void testSupplier() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("Hello John go 
to number 2", "Hi Mary go to number 1");
 
-        template.sendBody("direct:shamrock", "Jack");
-        template.sendBody("direct:moe", "Mary");
+        template.sendBody("direct:hello", "John");
+        template.sendBody("direct:hi", "Mary");
 
         assertMockEndpointsSatisfied();
     }
@@ -56,36 +50,36 @@ public class KameletLocalBeanTest extends CamelTestSupport {
             @Override
             public void configure() throws Exception {
                 routeTemplate("whereTo")
-                        .templateParameter("bar") // name of bar
-                        .configure(rtc -> {
-                            // create local bean with id myBar which can be 
used in the routes via {{myBar}}
-                            rtc.bind("myBar", MyBar.class, new 
MyBar(rtc.getProperty("bar", String.class)));
-                        })
+                        .templateParameter("foo")
+                        .templateBean("myBar", () -> new 
MyStaticBar(counter.incrementAndGet()))
                         .from("kamelet:source")
                         // must use {{myBar}} to refer to the local bean
+                        .setBody(simple("{{foo}} ${body}"))
                         .to("bean:{{myBar}}");
 
-                from("direct:shamrock")
-                        .kamelet("whereTo?bar=Shamrock")
+                from("direct:hi")
+                        .kamelet("whereTo?foo=Hi")
                         .to("mock:result");
 
-                from("direct:moe")
-                        .kamelet("whereTo?bar=Moes")
+                from("direct:hello")
+                        .kamelet("whereTo?foo=Hello")
                         .to("mock:result");
             }
         };
     }
 
-    private static class MyBar {
+    private class MyStaticBar {
 
-        private final String bar;
+        private int number;
 
-        public MyBar(String bar) {
-            this.bar = bar;
+        public MyStaticBar(int number) {
+            this.number = number;
         }
 
-        public String where(String name) {
-            return "Hi " + name + " we are going to " + bar;
+        public String whereTo(String name) {
+            return name + " go to number " + number;
         }
+
     }
+
 }
diff --git 
a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTest.java
 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTest.java
index 3e72efd..df98bd9 100644
--- 
a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTest.java
+++ 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTest.java
@@ -57,10 +57,7 @@ public class KameletLocalBeanTest extends CamelTestSupport {
             public void configure() throws Exception {
                 routeTemplate("whereTo")
                         .templateParameter("bar") // name of bar
-                        .configure(rtc -> {
-                            // create local bean with id myBar which can be 
used in the routes via {{myBar}}
-                            rtc.bind("myBar", MyBar.class, new 
MyBar(rtc.getProperty("bar", String.class)));
-                        })
+                        .templateBean("myBar", MyBar.class, rtc -> new 
MyBar(rtc.getProperty("bar", String.class)))
                         .from("kamelet:source")
                         // must use {{myBar}} to refer to the local bean
                         .to("bean:{{myBar}}");
diff --git 
a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/MyInjectBar.java
 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/MyInjectBar.java
new file mode 100644
index 0000000..9321ecd
--- /dev/null
+++ 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/MyInjectBar.java
@@ -0,0 +1,29 @@
+/*
+ * 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.component.kamelet;
+
+import org.apache.camel.PropertyInject;
+
+public class MyInjectBar {
+
+    @PropertyInject("{{bar}}")
+    private String bar;
+
+    public String where(String name) {
+        return "Hi " + name + " we are going to " + bar;
+    }
+}
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/RouteTemplateContext.java 
b/core/camel-api/src/main/java/org/apache/camel/RouteTemplateContext.java
index b03993f..b1030b9 100644
--- a/core/camel-api/src/main/java/org/apache/camel/RouteTemplateContext.java
+++ b/core/camel-api/src/main/java/org/apache/camel/RouteTemplateContext.java
@@ -28,6 +28,11 @@ import org.apache.camel.spi.HasCamelContext;
  */
 public interface RouteTemplateContext extends HasCamelContext {
 
+    @FunctionalInterface
+    interface BeanSupplier<T> {
+        T get(RouteTemplateContext rtc);
+    }
+
     /**
      * Binds the bean to the repository (if possible).
      *
@@ -121,13 +126,17 @@ public interface RouteTemplateContext extends 
HasCamelContext {
     BeanRepository getLocalBeanRepository();
 
     /**
-     * Sets a configurer which allows to do configuration while the route 
template is being used to create a route. This
-     * gives control over the creating process, such as binding local beans 
and doing other kind of customization.
+     * Sets a custom configurer which allows to do configuration while the 
route template is being used to create a
+     * route. This gives control over the creating process, such as binding 
local beans and doing other kind of
+     * customization.
      *
      * @param configurer the configurer with callback to invoke with the given 
route template context
      */
     void setConfigurer(Consumer<RouteTemplateContext> configurer);
 
+    /**
+     * Gets the custom configurer.
+     */
     Consumer<RouteTemplateContext> getConfigurer();
 
 }
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 5c9e751..425d81c 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
@@ -43,6 +43,7 @@ import 
org.apache.camel.model.Resilience4jConfigurationDefinition;
 import org.apache.camel.model.RouteDefinition;
 import org.apache.camel.model.RouteDefinitionHelper;
 import org.apache.camel.model.RouteFilters;
+import org.apache.camel.model.RouteTemplateBeanDefinition;
 import org.apache.camel.model.RouteTemplateDefinition;
 import org.apache.camel.model.RouteTemplateParameterDefinition;
 import org.apache.camel.model.cloud.ServiceCallConfigurationDefinition;
@@ -298,6 +299,28 @@ public class DefaultModel implements Model {
         def.setTemplateParameters(prop);
         def.setRouteTemplateContext(routeTemplateContext);
 
+        // setup local beans
+        if (target.getTemplateBeans() != null) {
+            for (RouteTemplateBeanDefinition b : target.getTemplateBeans()) {
+                if (b.getBeanType() != null) {
+                    // could be created via XML DSL where you cannot program 
in Java and can only specify the bean as fqn classname
+                    Class<?> clazz = 
camelContext.getClassResolver().resolveMandatoryClass(b.getBeanType());
+                    routeTemplateContext.bind(b.getName(), clazz, () -> 
camelContext.getInjector().newInstance(clazz));
+                } else if (b.getBeanSupplier() != null) {
+                    // bean class is optional for supplier
+                    if (b.getBeanClass() != null) {
+                        routeTemplateContext.bind(b.getName(), 
b.getBeanClass(), b.getBeanSupplier());
+                    } else {
+                        routeTemplateContext.bind(b.getName(), 
b.getBeanSupplier());
+                    }
+                } else if (b.getBeanClass() != null) {
+                    // we only have the bean class so we use that to create a 
new bean via the injector
+                    routeTemplateContext.bind(b.getName(), b.getBeanClass(),
+                            () -> 
camelContext.getInjector().newInstance(b.getBeanClass()));
+                }
+            }
+        }
+
         if (target.getConfigurer() != null) {
             routeTemplateContext.setConfigurer(target.getConfigurer());
         }
diff --git 
a/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties
 
b/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties
index 73ac79f..ba47a35 100644
--- 
a/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties
+++ 
b/core/camel-core-model/src/generated/resources/META-INF/services/org/apache/camel/model.properties
@@ -169,6 +169,7 @@ stop
 stream-config
 syslog
 tarfile
+templateBean
 templateParameter
 threadPoolProfile
 threads
diff --git 
a/core/camel-core-model/src/generated/resources/org/apache/camel/model/jaxb.index
 
b/core/camel-core-model/src/generated/resources/org/apache/camel/model/jaxb.index
index 916509a..88bee1b 100644
--- 
a/core/camel-core-model/src/generated/resources/org/apache/camel/model/jaxb.index
+++ 
b/core/camel-core-model/src/generated/resources/org/apache/camel/model/jaxb.index
@@ -65,6 +65,7 @@ RollbackDefinition
 RouteBuilderDefinition
 RouteContextRefDefinition
 RouteDefinition
+RouteTemplateBeanDefinition
 RouteTemplateContextRefDefinition
 RouteTemplateDefinition
 RouteTemplateParameterDefinition
diff --git 
a/core/camel-core-model/src/generated/resources/org/apache/camel/model/routeTemplate.json
 
b/core/camel-core-model/src/generated/resources/org/apache/camel/model/routeTemplate.json
index ea3c4c5..d5ac94b 100644
--- 
a/core/camel-core-model/src/generated/resources/org/apache/camel/model/routeTemplate.json
+++ 
b/core/camel-core-model/src/generated/resources/org/apache/camel/model/routeTemplate.json
@@ -12,6 +12,7 @@
   },
   "properties": {
     "templateParameter": { "kind": "element", "displayName": "Template 
Parameter", "required": false, "type": "array", "javaType": 
"java.util.List<org.apache.camel.model.RouteTemplateParameterDefinition>", 
"deprecated": false, "autowired": false, "secret": false, "description": "Adds 
a parameter the route template uses." },
+    "templateBean": { "kind": "element", "displayName": "Template Bean", 
"required": false, "type": "array", "javaType": 
"java.util.List<org.apache.camel.model.RouteTemplateBeanDefinition>", 
"deprecated": false, "autowired": false, "secret": false },
     "route": { "kind": "element", "displayName": "Route", "required": true, 
"type": "object", "javaType": "org.apache.camel.model.RouteDefinition", 
"deprecated": false, "autowired": false, "secret": false, "description": "To 
define the route in the template" },
     "id": { "kind": "attribute", "displayName": "Id", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "Sets the id of this node" 
},
     "description": { "kind": "element", "displayName": "Description", 
"required": false, "type": "object", "javaType": 
"org.apache.camel.model.DescriptionDefinition", "deprecated": false, 
"autowired": false, "secret": false, "description": "Sets the description of 
this node" }
diff --git 
a/core/camel-core-model/src/generated/resources/org/apache/camel/model/templateBean.json
 
b/core/camel-core-model/src/generated/resources/org/apache/camel/model/templateBean.json
new file mode 100644
index 0000000..f2bc7ce
--- /dev/null
+++ 
b/core/camel-core-model/src/generated/resources/org/apache/camel/model/templateBean.json
@@ -0,0 +1,17 @@
+{
+  "model": {
+    "kind": "model",
+    "name": "templateBean",
+    "title": "Template Bean",
+    "description": "A route template bean (local bean)",
+    "deprecated": false,
+    "label": "configuration",
+    "javaType": "org.apache.camel.model.RouteTemplateBeanDefinition",
+    "input": false,
+    "output": false
+  },
+  "properties": {
+    "name": { "kind": "attribute", "displayName": "Name", "required": true, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "Bean name" },
+    "beanType": { "kind": "attribute", "displayName": "Bean Type", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": false, "description": "Sets the Class of the 
bean" }
+  }
+}
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/builder/TemplatedRouteBuilder.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/builder/TemplatedRouteBuilder.java
index fc43ad6..9f5bd6a 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/builder/TemplatedRouteBuilder.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/builder/TemplatedRouteBuilder.java
@@ -94,7 +94,7 @@ public final class TemplatedRouteBuilder {
      * @param id   the id of the bean
      * @param bean the bean
      */
-    public TemplatedRouteBuilder bind(String id, Object bean) {
+    public TemplatedRouteBuilder bean(String id, Object bean) {
         routeTemplateContext.bind(id, bean);
         return this;
     }
@@ -106,7 +106,7 @@ public final class TemplatedRouteBuilder {
      * @param type the type of the bean to associate the binding
      * @param bean the bean
      */
-    public TemplatedRouteBuilder bind(String id, Class<?> type, Object bean) {
+    public TemplatedRouteBuilder bean(String id, Class<?> type, Object bean) {
         routeTemplateContext.bind(id, type, bean);
         return this;
     }
@@ -118,7 +118,7 @@ public final class TemplatedRouteBuilder {
      * @param type the type of the bean to associate the binding
      * @param bean the bean
      */
-    public TemplatedRouteBuilder bind(String id, Class<?> type, 
Supplier<Object> bean) {
+    public TemplatedRouteBuilder bean(String id, Class<?> type, 
Supplier<Object> bean) {
         routeTemplateContext.bind(id, type, bean);
         return this;
     }
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/DefaultRouteTemplateContext.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/DefaultRouteTemplateContext.java
index e5b490b..4e81852 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/DefaultRouteTemplateContext.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/DefaultRouteTemplateContext.java
@@ -53,12 +53,25 @@ public final class DefaultRouteTemplateContext implements 
RouteTemplateContext {
 
     @Override
     public void bind(String id, Object bean) {
-        registry.bind(id, bean);
+        if (bean instanceof BeanSupplier) {
+            // need to unwrap bean supplier as regular supplier
+            BeanSupplier<Object> bs = (BeanSupplier<Object>) bean;
+            registry.bind(id, (Supplier<Object>) () -> 
bs.get(DefaultRouteTemplateContext.this));
+        } else {
+            registry.bind(id, bean);
+        }
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public void bind(String id, Class<?> type, Object bean) {
-        registry.bind(id, type, bean);
+        if (bean instanceof BeanSupplier) {
+            // need to unwrap bean supplier as regular supplier
+            BeanSupplier<Object> bs = (BeanSupplier<Object>) bean;
+            registry.bind(id, type, () -> bs.get(this));
+        } else {
+            registry.bind(id, type, bean);
+        }
     }
 
     @Override
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateBeanDefinition.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateBeanDefinition.java
new file mode 100644
index 0000000..3481daf
--- /dev/null
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateBeanDefinition.java
@@ -0,0 +1,94 @@
+/*
+ * 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.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+
+import org.apache.camel.RouteTemplateContext;
+import org.apache.camel.spi.Metadata;
+
+/**
+ * A route template bean (local bean)
+ */
+@Metadata(label = "configuration")
+@XmlRootElement(name = "templateBean")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class RouteTemplateBeanDefinition {
+    @XmlAttribute(required = true)
+    String name;
+    @XmlAttribute
+    private String beanType;
+    @XmlTransient
+    private Class<?> beanClass;
+    @XmlTransient
+    private RouteTemplateContext.BeanSupplier<Object> beanSupplier;
+
+    public RouteTemplateBeanDefinition() {
+    }
+
+    public RouteTemplateBeanDefinition(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Bean name
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getBeanType() {
+        return beanType;
+    }
+
+    /**
+     * Sets the Class of the bean
+     */
+    public void setBeanType(String beanType) {
+        this.beanType = beanType;
+    }
+
+    public Class<?> getBeanClass() {
+        return beanClass;
+    }
+
+    /**
+     * Sets the Class of the bean
+     */
+    public void setBeanType(Class<?> beanType) {
+        this.beanClass = beanType;
+    }
+
+    public RouteTemplateContext.BeanSupplier<Object> getBeanSupplier() {
+        return beanSupplier;
+    }
+
+    /**
+     * Bean supplier that uses lambda style to create the local bean
+     */
+    public void setBeanSupplier(RouteTemplateContext.BeanSupplier<Object> 
beanSupplier) {
+        this.beanSupplier = beanSupplier;
+    }
+}
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateDefinition.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateDefinition.java
index 8fd4f84..21a0fc7 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateDefinition.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteTemplateDefinition.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -39,12 +40,14 @@ import org.apache.camel.spi.Metadata;
  */
 @Metadata(label = "configuration")
 @XmlRootElement(name = "routeTemplate")
-@XmlType(propOrder = { "templateParameters", "route" })
+@XmlType(propOrder = { "templateParameters", "templateBeans", "route" })
 @XmlAccessorType(XmlAccessType.FIELD)
 public class RouteTemplateDefinition extends OptionalIdentifiedDefinition {
 
     @XmlElement(name = "templateParameter")
     private List<RouteTemplateParameterDefinition> templateParameters;
+    @XmlElement(name = "templateBean")
+    private List<RouteTemplateBeanDefinition> templateBeans;
     @XmlElement(name = "route", required = true)
     private RouteDefinition route = new RouteDefinition();
     @XmlTransient
@@ -54,14 +57,18 @@ public class RouteTemplateDefinition extends 
OptionalIdentifiedDefinition {
         return templateParameters;
     }
 
-    public Consumer<RouteTemplateContext> getConfigurer() {
-        return configurer;
-    }
-
     public void setTemplateParameters(List<RouteTemplateParameterDefinition> 
templateParameters) {
         this.templateParameters = templateParameters;
     }
 
+    public List<RouteTemplateBeanDefinition> getTemplateBeans() {
+        return templateBeans;
+    }
+
+    public void setTemplateBeans(List<RouteTemplateBeanDefinition> 
templateBeans) {
+        this.templateBeans = templateBeans;
+    }
+
     public RouteDefinition getRoute() {
         return route;
     }
@@ -70,6 +77,14 @@ public class RouteTemplateDefinition extends 
OptionalIdentifiedDefinition {
         this.route = route;
     }
 
+    public void setConfigurer(Consumer<RouteTemplateContext> configurer) {
+        this.configurer = configurer;
+    }
+
+    public Consumer<RouteTemplateContext> getConfigurer() {
+        return configurer;
+    }
+
     // Fluent API
     // 
-------------------------------------------------------------------------
 
@@ -161,6 +176,84 @@ public class RouteTemplateDefinition extends 
OptionalIdentifiedDefinition {
     }
 
     /**
+     * Adds a local bean the route template uses.
+     *
+     * @param name the name of the bean
+     * @param type the type of the bean to associate the binding
+     */
+    public RouteTemplateDefinition templateBean(String name, Class<?> type) {
+        if (templateBeans == null) {
+            templateBeans = new ArrayList<>();
+        }
+        RouteTemplateBeanDefinition def = new RouteTemplateBeanDefinition();
+        def.setName(name);
+        def.setBeanType(type);
+        templateBeans.add(def);
+        return this;
+    }
+
+    /**
+     * Adds a local bean the route template uses.
+     *
+     * @param name the name of the bean
+     * @param bean the bean or a supplier for the bean
+     */
+    @SuppressWarnings("unchecked")
+    public RouteTemplateDefinition templateBean(String name, Object bean) {
+        if (templateBeans == null) {
+            templateBeans = new ArrayList<>();
+        }
+        RouteTemplateBeanDefinition def = new RouteTemplateBeanDefinition();
+        def.setName(name);
+        if (bean instanceof RouteTemplateContext.BeanSupplier) {
+            def.setBeanSupplier((RouteTemplateContext.BeanSupplier<Object>) 
bean);
+        } else if (bean instanceof Supplier) {
+            def.setBeanSupplier((ctx) -> ((Supplier<?>) bean).get());
+        } else {
+            def.setBeanSupplier((ctx) -> bean);
+            def.setBeanType(bean.getClass());
+        }
+        templateBeans.add(def);
+        return this;
+    }
+
+    /**
+     * Adds a local bean the route template uses.
+     *
+     * @param name the name of the bean
+     * @param bean the supplier for the bean
+     */
+    public RouteTemplateDefinition templateBean(String name, Supplier<Object> 
bean) {
+        if (templateBeans == null) {
+            templateBeans = new ArrayList<>();
+        }
+        RouteTemplateBeanDefinition def = new RouteTemplateBeanDefinition();
+        def.setName(name);
+        def.setBeanSupplier((ctx) -> ((Supplier<?>) bean).get());
+        templateBeans.add(def);
+        return this;
+    }
+
+    /**
+     * Adds a local bean the route template uses.
+     *
+     * @param name the name of the bean
+     * @param type the type of the bean to associate the binding
+     * @param bean a supplier for the bean
+     */
+    public RouteTemplateDefinition templateBean(String name, Class<?> type, 
RouteTemplateContext.BeanSupplier<Object> bean) {
+        if (templateBeans == null) {
+            templateBeans = new ArrayList<>();
+        }
+        RouteTemplateBeanDefinition def = new RouteTemplateBeanDefinition();
+        def.setName(name);
+        def.setBeanType(type);
+        def.setBeanSupplier(bean);
+        templateBeans.add(def);
+        return this;
+    }
+
+    /**
      * Sets a configurer which allows to do configuration while the route 
template is being used to create a route. This
      * gives control over the creating process, such as binding local beans 
and doing other kind of customization.
      *
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 7bbce3c..f998fed 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
@@ -17,6 +17,7 @@
 package org.apache.camel.builder;
 
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
 
 import org.apache.camel.ContextTestSupport;
 import org.apache.camel.Exchange;
@@ -82,7 +83,7 @@ public class RouteTemplateLocalBeanTest extends 
ContextTestSupport {
         TemplatedRouteBuilder.builder(context, "myTemplate")
                 .parameter("foo", "one")
                 .parameter("bar", "myBar")
-                .bind("myBar", (Processor) ex -> 
ex.getMessage().setBody("Builder " + ex.getMessage().getBody()))
+                .bean("myBar", (Processor) ex -> 
ex.getMessage().setBody("Builder " + ex.getMessage().getBody()))
                 .routeId("myRoute")
                 .add();
 
@@ -113,14 +114,14 @@ public class RouteTemplateLocalBeanTest extends 
ContextTestSupport {
         TemplatedRouteBuilder.builder(context, "myTemplate")
                 .parameter("foo", "one")
                 .parameter("bar", "myBar")
-                .bind("myBar", new BuilderProcessor())
+                .bean("myBar", new BuilderProcessor())
                 .routeId("myRoute")
                 .add();
 
         TemplatedRouteBuilder.builder(context, "myTemplate")
                 .parameter("foo", "two")
                 .parameter("bar", "myBar")
-                .bind("myBar", new BuilderTwoProcessor())
+                .bean("myBar", new BuilderTwoProcessor())
                 .routeId("myRoute2")
                 .add();
 
@@ -219,7 +220,7 @@ public class RouteTemplateLocalBeanTest extends 
ContextTestSupport {
     }
 
     @Test
-    public void testLocalBeanInTemplate() throws Exception {
+    public void testLocalBeanInTemplateConfigure() throws Exception {
         context.addRoutes(new RouteBuilder() {
             @Override
             public void configure() throws Exception {
@@ -251,7 +252,7 @@ public class RouteTemplateLocalBeanTest extends 
ContextTestSupport {
     }
 
     @Test
-    public void testLocalBeanInTemplateTwo() throws Exception {
+    public void testLocalBeanInTemplateConfigureTwo() throws Exception {
         final AtomicInteger counter = new AtomicInteger();
 
         context.addRoutes(new RouteBuilder() {
@@ -295,6 +296,162 @@ public class RouteTemplateLocalBeanTest extends 
ContextTestSupport {
         context.stop();
     }
 
+    @Test
+    public void testLocalBeanInTemplateBeanSupplier() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                
routeTemplate("myTemplate").templateParameter("foo").templateParameter("bar")
+                        .templateBean("myBar", Processor.class,
+                                (ctx) -> (Processor) ex -> 
ex.getMessage().setBody("Builder " + ex.getMessage().getBody()))
+                        .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("Builder World", out);
+
+        // should not be a global bean
+        assertNull(context.getRegistry().lookupByName("myBar"));
+
+        context.stop();
+    }
+
+    @Test
+    public void testLocalBeanInTemplateBeanSupplierTwo() throws Exception {
+        final AtomicInteger counter = new AtomicInteger();
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                
routeTemplate("myTemplate").templateParameter("foo").templateParameter("bar")
+                        .templateBean("myBar", Processor.class, (ctx) -> 
(Processor) ex -> ex.getMessage().setBody("Builder" +
+                                                                               
                                    counter.incrementAndGet()
+                                                                               
                                    + " "
+                                                                               
                                    + ex.getMessage()
+                                                                               
                                            .getBody()))
+                        .from("direct:{{foo}}")
+                        .to("bean:{{bar}}");
+            }
+        });
+
+        context.start();
+
+        TemplatedRouteBuilder.builder(context, "myTemplate")
+                .parameter("foo", "one")
+                .parameter("bar", "myBar")
+                .routeId("myRoute")
+                .add();
+
+        TemplatedRouteBuilder.builder(context, "myTemplate")
+                .parameter("foo", "two")
+                .parameter("bar", "myBar")
+                .routeId("myRoute2")
+                .add();
+
+        assertEquals(2, context.getRoutes().size());
+
+        Object out = template.requestBody("direct:one", "World");
+        assertEquals("Builder1 World", out);
+        Object out2 = template.requestBody("direct:two", "Camel");
+        assertEquals("Builder2 Camel", out2);
+
+        // should not be a global bean
+        assertNull(context.getRegistry().lookupByName("myBar"));
+
+        context.stop();
+    }
+
+    @Test
+    public void testLocalBeanInTemplateBean() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                
routeTemplate("myTemplate").templateParameter("foo").templateParameter("bar")
+                        .templateBean("myBar",
+                                (Supplier<Processor>) () -> ex -> 
ex.getMessage()
+                                        .setBody("Builder " + 
ex.getMessage().getBody()))
+                        .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("Builder World", out);
+
+        // should not be a global bean
+        assertNull(context.getRegistry().lookupByName("myBar"));
+
+        context.stop();
+    }
+
+    @Test
+    public void testLocalBeanInTemplateBeanTwo() throws Exception {
+        final AtomicInteger counter = new AtomicInteger();
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                
routeTemplate("myTemplate").templateParameter("foo").templateParameter("bar")
+                        .templateBean("myBar", (Supplier<Processor>) () -> ex 
-> ex.getMessage().setBody("Builder"
+                                                                               
                          + counter
+                                                                               
                                  .incrementAndGet()
+                                                                               
                          + " "
+                                                                               
                          + ex.getMessage()
+                                                                               
                                  .getBody()))
+                        .from("direct:{{foo}}")
+                        .to("bean:{{bar}}");
+            }
+        });
+
+        context.start();
+
+        TemplatedRouteBuilder.builder(context, "myTemplate")
+                .parameter("foo", "one")
+                .parameter("bar", "myBar")
+                .routeId("myRoute")
+                .add();
+
+        TemplatedRouteBuilder.builder(context, "myTemplate")
+                .parameter("foo", "two")
+                .parameter("bar", "myBar")
+                .routeId("myRoute2")
+                .add();
+
+        assertEquals(2, context.getRoutes().size());
+
+        Object out = template.requestBody("direct:one", "World");
+        assertEquals("Builder1 World", out);
+        Object out2 = template.requestBody("direct:two", "Camel");
+        assertEquals("Builder2 Camel", out2);
+
+        // should not be a global bean
+        assertNull(context.getRegistry().lookupByName("myBar"));
+
+        context.stop();
+    }
+
     private class BuilderProcessor implements Processor {
 
         @Override
diff --git 
a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java 
b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
index d7262c7..74549bf 100644
--- 
a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
+++ 
b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
@@ -1020,6 +1020,16 @@ public class ModelParser extends BaseParser {
             return true;
         }, optionalIdentifiedDefinitionElementHandler(), noValueHandler());
     }
+    protected RouteTemplateBeanDefinition doParseRouteTemplateBeanDefinition() 
throws IOException, XmlPullParserException {
+        return doParse(new RouteTemplateBeanDefinition(), (def, key, val) -> {
+            switch (key) {
+                case "beanType": def.setBeanType(val); break;
+                case "name": def.setName(val); break;
+                default: return false;
+            }
+            return true;
+        }, noElementHandler(), noValueHandler());
+    }
     protected RouteTemplateContextRefDefinition 
doParseRouteTemplateContextRefDefinition() throws IOException, 
XmlPullParserException {
         return doParse(new RouteTemplateContextRefDefinition(), (def, key, 
val) -> {
             if ("ref".equals(key)) {
@@ -1034,6 +1044,7 @@ public class ModelParser extends BaseParser {
             optionalIdentifiedDefinitionAttributeHandler(), (def, key) -> {
             switch (key) {
                 case "route": def.setRoute(doParseRouteDefinition()); break;
+                case "templateBean": 
doAdd(doParseRouteTemplateBeanDefinition(), def.getTemplateBeans(), 
def::setTemplateBeans); break;
                 case "templateParameter": 
doAdd(doParseRouteTemplateParameterDefinition(), def.getTemplateParameters(), 
def::setTemplateParameters); break;
                 default: return 
optionalIdentifiedDefinitionElementHandler().accept(def, key);
             }
diff --git a/docs/user-manual/modules/ROOT/pages/route-template.adoc 
b/docs/user-manual/modules/ROOT/pages/route-template.adoc
index 070168c..4a22706 100644
--- a/docs/user-manual/modules/ROOT/pages/route-template.adoc
+++ b/docs/user-manual/modules/ROOT/pages/route-template.adoc
@@ -121,20 +121,15 @@ TemplatedRouteBuilder.builder(context, "myTemplate")
 The route template allows to bind beans which is local scoped and only used as 
part of creating routes from the template.
 This allows to use the same template to create multiple routes, where beans 
are local (private) for each created route.
 
-For example given the following route template, then we can configure the 
bindings (and other things) as shown:
+For example given the following route template where we use `templateBean` to 
setup the local bean as shown:
 
 [source,java]
 ----
 routeTemplate("s3template")
     .templateParameter("region")
     .templateParameter("bucket")
-    .configure((RouteTemplateContext rtc) ->
-        // name of local bean is myClient
-        rtc.bind("myClient", S3Client.class,
-                S3Client.builder()
-                    .region(rtc.getProperty("region", Region.class))
-                    .build();
-        )
+    .templateBean("myClient", S3Client.class, rtc ->
+            S3Client.builder().region(rtc.getProperty("region", 
Region.class)).build();
     )
     .from("direct:s3-store")
      // must refer to the bean with {{myClient}}
@@ -144,12 +139,14 @@ routeTemplate("s3template")
 The template has two parameters to specify the AWS region and the S3 bucket. 
To connect to S3
 then a `software.amazon.awssdk.services.s3.S3Client` bean is needed.
 
-To create this bean we can use bind a bean with the id `myClient` that is 
bound as `java.util.Supplier`.
-The bind operation needs to know the class type that the supplier would 
return, and therefore you must
-specify `S3Client.class` as parameter.
+To create this bean we specify this with the `templateBean` DSL where we 
specify the bean id as `myClient`.
+The type of the bean can be specified (`S3Client.class`) however its optional
+(can be use if you need to let beans be discovered by type and not name).
+
+This ensures that the code creating the bean is executed later ( when Camel is 
creating a route from the template),
+then the code must be specified as a _supplier_. Because we want during 
creation of the bean access to template parameters,
+we use a Camel `BeanSupplier` which gives access to `RouteTemplateContext` 
that is the _rtc_ variable in the code above.
 
-This ensures that the code creating the bean is executed later when Camel is 
creating a route from the template.
-This makes the bean with id `myClient` a local bean that is only in the scope 
(namespace) in the route template.
 *Important:* the local bean with id `myClient` *must* be referred to using 
Camel's property placeholder syntax, eg `{{myClient}}`
 in the route template, as shown above with the _to_ endpoint. This is because 
the local
 bean must be made unique and Camel will internally re-assign the bean id to 
use an unique id instead of `myClient`. And this is done with the help
@@ -188,7 +185,7 @@ if you desire the bean to be local scoped (not shared with 
others):
 TemplatedRouteBuilder.builder(context, "s3template")
     .parameter("region", "US-EAST-1")
     .parameter("bucket", "myBucket")
-    .bind("myClient", S3Client.class,
+    .bean("myClient", S3Client.class,
                 S3Client.builder()
                     .region(rtc.getProperty("region", Region.class))
                     .build())
@@ -196,7 +193,7 @@ TemplatedRouteBuilder.builder(context, "s3template")
     .add();
 ----
 
-As you can see the binding is similar to when binding directly in the route 
template.
+As you can see the binding is similar to when using `templateBean` directly in 
the route template.
 
 Instead of binding the beans from the template builder, you could also create 
the bean outside the template,
 and bind it by reference.
@@ -209,11 +206,32 @@ final S3Client myClient = 
S3Client.builder().region(Region.US_EAST_1).build();
 TemplatedRouteBuilder.builder(context, "s3template")
     .parameter("region", Region.US_EAST_1)
     .parameter("bucket", "myBucket")
-    .bind("myClient", myClient)
+    .bean("myClient", myClient)
     .routeId("mys3route")
     .add();
 ----
 
+You should prefer to create the local beans directly from within the template 
(if possible) because this
+ensures the route template has this out of the box. Otherwise the bean must be 
created or provided every time
+a new route is created from the route template. However the latter gives 
freedom to create the bean in any other custom way.
+
+== Configuring route templates when creating route
+
+There may be some special situations where you want to be able to do some 
custom configuration/code when
+a route is about to be created from a route template. To support this you can 
use the `configure` in the route template DSL
+where you can specify the code to execute as show:
+
+[source,java]
+----
+routeTemplate("myTemplate")
+    .templateParameter("myTopic")
+    .configure((RouteTemplateContext rtc) ->
+        // do some custom code here
+    )
+    .from("direct:to-topic")
+    .to("kafka:{{myTopic}}");
+----
+
 == JMX management
 
 The route templates can be dumped as XML from the `ManagedCamelContextMBean` 
MBean via the `dumpRouteTemplatesAsXml` operation.

Reply via email to