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 08a581a  CAMEL-16394: Route Template local beans.
08a581a is described below

commit 08a581a85cee980aa5c9244f64f42ebc2136118b
Author: Claus Ibsen <claus.ib...@gmail.com>
AuthorDate: Fri May 7 07:26:43 2021 +0200

    CAMEL-16394: Route Template local beans.
---
 .../component/kamelet/KameletLocalBeanTest.java    | 91 ++++++++++++++++++++++
 .../org/apache/camel/RouteTemplateContext.java     | 12 +++
 .../org/apache/camel/impl/DefaultCamelContext.java | 34 ++++++--
 .../java/org/apache/camel/impl/DefaultModel.java   | 28 +------
 .../camel/builder/TemplatedRouteBuilder.java       |  3 +-
 .../camel/model/DefaultRouteTemplateContext.java   | 12 +++
 .../modules/ROOT/pages/route-template.adoc         | 16 ++--
 7 files changed, 158 insertions(+), 38 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/KameletLocalBeanTest.java
new file mode 100644
index 0000000..3e72efd
--- /dev/null
+++ 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.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");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testTwo() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("Hi Jack we are 
going to Shamrock",
+                "Hi Mary we are going to Moes");
+
+        template.sendBody("direct:shamrock", "Jack");
+        template.sendBody("direct:moe", "Mary");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    // **********************************************
+    //
+    // test set-up
+    //
+    // **********************************************
+
+    @Obsolete
+    protected RoutesBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @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)));
+                        })
+                        .from("kamelet:source")
+                        // must use {{myBar}} to refer to the local bean
+                        .to("bean:{{myBar}}");
+
+                from("direct:shamrock")
+                        .kamelet("whereTo?bar=Shamrock")
+                        .to("mock:result");
+
+                from("direct:moe")
+                        .kamelet("whereTo?bar=Moes")
+                        .to("mock:result");
+            }
+        };
+    }
+
+    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/core/camel-api/src/main/java/org/apache/camel/RouteTemplateContext.java 
b/core/camel-api/src/main/java/org/apache/camel/RouteTemplateContext.java
index 80c3a5a..b03993f 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
@@ -17,6 +17,7 @@
 package org.apache.camel;
 
 import java.util.Map;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 import org.apache.camel.spi.BeanRepository;
@@ -118,4 +119,15 @@ public interface RouteTemplateContext extends 
HasCamelContext {
      * Gets the local bean repository for the route template when creating the 
new route
      */
     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.
+     *
+     * @param configurer the configurer with callback to invoke with the given 
route template context
+     */
+    void setConfigurer(Consumer<RouteTemplateContext> configurer);
+
+    Consumer<RouteTemplateContext> getConfigurer();
+
 }
diff --git 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
index a5d7e1b..20a7a1f 100644
--- 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
+++ 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultCamelContext.java
@@ -751,19 +751,23 @@ public class DefaultCamelContext extends 
SimpleCamelContext implements ModelCame
                 if (routeDefinition.isTemplate() != null && 
routeDefinition.isTemplate()
                         && routeDefinition.getTemplateParameters() != null) {
 
+                    // apply configurer if any present
+                    if 
(routeDefinition.getRouteTemplateContext().getConfigurer() != null) {
+                        
routeDefinition.getRouteTemplateContext().getConfigurer()
+                                
.accept(routeDefinition.getRouteTemplateContext());
+                    }
+
                     // copy parameters/bean repository to not cause side-effect
                     Map<String, Object> params = new 
HashMap<>(routeDefinition.getTemplateParameters());
                     LocalBeanRegistry bbr
                             = (LocalBeanRegistry) 
routeDefinition.getRouteTemplateContext().getLocalBeanRepository();
-                    if (bbr != null) {
-                        bbr = bbr.copy();
-                    }
+                    LocalBeanRegistry bbrCopy = new LocalBeanRegistry();
 
                     // make all bean in the bean repository use unique keys 
(need to add uuid counter)
                     // so when the route template is used again to create 
another route, then there is
                     // no side-effect from previously used values that Camel 
may use in its endpoint
                     // registry and elsewhere
-                    if (bbr != null) {
+                    if (bbr != null && !bbr.isEmpty()) {
                         for (Map.Entry<String, Object> param : 
params.entrySet()) {
                             Object value = param.getValue();
                             if (value instanceof String) {
@@ -774,11 +778,27 @@ public class DefaultCamelContext extends 
SimpleCamelContext implements ModelCame
                                     LOG.debug(
                                             "Route: {} re-assigning local-bean 
id: {} to: {} to ensure ids are globally unique",
                                             routeDefinition.getId(), oldKey, 
newKey);
-                                    bbr.swapKey(oldKey, newKey);
+                                    bbrCopy.put(newKey, bbr.remove(oldKey));
                                     param.setValue(newKey);
                                 }
                             }
                         }
+                        // the remainder of the local beans must also have 
their ids made global unique
+                        for (String oldKey : bbr.keySet()) {
+                            String newKey = oldKey + "-" + UUID.generateUuid();
+                            LOG.debug(
+                                    "Route: {} re-assigning local-bean id: {} 
to: {} to ensure ids are globally unique",
+                                    routeDefinition.getId(), oldKey, newKey);
+                            bbrCopy.put(newKey, bbr.get(oldKey));
+                            if (!params.containsKey(oldKey)) {
+                                // if a bean was bound as local bean with a 
key and it was not defined as template parameter
+                                // then store it as if it was a template 
parameter with same key=value which allows us
+                                // to use this local bean in the route without 
any problem such as:
+                                //   to("bean:{{myBean}}")
+                                // and myBean is the local bean id.
+                                params.put(oldKey, newKey);
+                            }
+                        }
                     }
 
                     Properties prop = new Properties();
@@ -786,8 +806,8 @@ public class DefaultCamelContext extends SimpleCamelContext 
implements ModelCame
                     pc.setLocalProperties(prop);
 
                     // we need to shadow the bean registry on the CamelContext 
with the local beans from the route template context
-                    if (localBeans != null && bbr != null) {
-                        localBeans.setLocalBeanRepository(bbr);
+                    if (localBeans != null && bbrCopy != null) {
+                        localBeans.setLocalBeanRepository(bbrCopy);
                     }
 
                     // need to reset auto assigned ids, so there is no clash 
when creating routes
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 7b727a8..5c9e751 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
@@ -22,7 +22,6 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
 import java.util.StringJoiner;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
@@ -274,29 +273,6 @@ public class DefaultModel implements Model {
             prop.putAll(routeTemplateContext.getParameters());
         }
 
-        // TODO: not sure if this is the best location where this should 
happen, eventually this can
-        //       be moved at a later stage in the camel context but would mean 
that we need to add the
-        //       configurer to the route definition.
-        try {
-            Properties localProps = new Properties();
-            localProps.putAll(prop);
-
-            
getCamelContext().getPropertiesComponent().setLocalProperties(localProps);
-
-            // apply configurer if any present
-            if (target.getConfigurer() != null) {
-                target.getConfigurer().accept(routeTemplateContext);
-            }
-        } finally {
-            
getCamelContext().getPropertiesComponent().setLocalProperties(null);
-        }
-
-        // override with user parameters part 2: user may have added parameter
-        // in the customize callback
-        if (routeTemplateContext.getParameters() != null) {
-            prop.putAll(routeTemplateContext.getParameters());
-        }
-
         RouteTemplateDefinition.Converter converter = 
RouteTemplateDefinition.Converter.DEFAULT_CONVERTER;
 
         for (Map.Entry<String, RouteTemplateDefinition.Converter> entry : 
routeTemplateConverters.entrySet()) {
@@ -322,6 +298,10 @@ public class DefaultModel implements Model {
         def.setTemplateParameters(prop);
         def.setRouteTemplateContext(routeTemplateContext);
 
+        if (target.getConfigurer() != null) {
+            routeTemplateContext.setConfigurer(target.getConfigurer());
+        }
+
         // assign ids to the routes and validate that the id's are all unique
         String duplicate = RouteDefinitionHelper.validateUniqueIds(def, 
routeDefinitions);
         if (duplicate != null) {
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 05a31eb..fc43ad6 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
@@ -161,8 +161,9 @@ public final class TemplatedRouteBuilder {
                 }
                 handler.accept(def);
             }
+            // configurer is executed later controlled by the route template 
context
             if (configurer != null) {
-                configurer.accept(routeTemplateContext);
+                routeTemplateContext.setConfigurer(configurer);
             }
             return camelContext.addRouteFromTemplate(routeId, routeTemplateId, 
routeTemplateContext);
         } catch (Exception e) {
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 f77cc5a..e5b490b 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
@@ -18,6 +18,7 @@ package org.apache.camel.model;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 import javax.xml.bind.annotation.XmlTransient;
@@ -36,6 +37,7 @@ public final class DefaultRouteTemplateContext implements 
RouteTemplateContext {
     private final CamelContext camelContext;
     private final LocalBeanRegistry registry;
     private final Map<String, Object> parameters;
+    private Consumer<RouteTemplateContext> configurer;
 
     public DefaultRouteTemplateContext(CamelContext camelContext) {
         this.camelContext = camelContext;
@@ -95,4 +97,14 @@ public final class DefaultRouteTemplateContext implements 
RouteTemplateContext {
     public BeanRepository getLocalBeanRepository() {
         return registry;
     }
+
+    @Override
+    public Consumer<RouteTemplateContext> getConfigurer() {
+        return configurer;
+    }
+
+    @Override
+    public void setConfigurer(Consumer<RouteTemplateContext> configurer) {
+        this.configurer = configurer;
+    }
 }
diff --git a/docs/user-manual/modules/ROOT/pages/route-template.adoc 
b/docs/user-manual/modules/ROOT/pages/route-template.adoc
index 76cebe6..070168c 100644
--- a/docs/user-manual/modules/ROOT/pages/route-template.adoc
+++ b/docs/user-manual/modules/ROOT/pages/route-template.adoc
@@ -121,8 +121,6 @@ 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.
 
-NOTE: Binding beans to route template is currently only available with Java 
DSL / Java coding.
-
 For example given the following route template, then we can configure the 
bindings (and other things) as shown:
 
 [source,java]
@@ -131,14 +129,16 @@ 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()
+                    .build();
         )
     )
     .from("direct:s3-store")
-    .to("aws2-s3:{{bucket}}?amazonS3Client=#myClient")
+     // must refer to the bean with {{myClient}}
+    .to("aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}")
 ----
 
 The template has two parameters to specify the AWS region and the S3 bucket. 
To connect to S3
@@ -150,6 +150,10 @@ specify `S3Client.class` as parameter.
 
 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
+of the property placeholder functionality.
 
 If multiple routes is created from this template, then each of the created 
routes, have their own
 S3Client bean created.
@@ -168,13 +172,13 @@ Suppose the route template below is defined in XML:
     <templateParameter name="bucket"/>
     <route>
       <from uri="direct:s3-store"/>
-      <to uri="aws2-s3:{{bucket}}?amazonS3Client=#myClient"/>
+      <to uri="aws2-s3:{{bucket}}?amazonS3Client=#{{myClient}}"/>
     </route>
   </routeTemplate>
 </camelContext>
 ----
 
-The template has no bean bindings for `#myClient` which would be required for 
creating the template.
+The template has no bean bindings for `#{{myClient}}` which would be required 
for creating the template.
 
 When creating routes form the template via `TemplatedRouteBuilder` then you 
can provide the bean binding
 if you desire the bean to be local scoped (not shared with others):

Reply via email to