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 2022/12/07 08:37:14 UTC

[camel] branch prefixId created (now 9565e46ab61)

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

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


      at 9565e46ab61 CAMEL-18798/CAMEL-18771: Allow to configure nodePrefixId on route/routeTemplate to prefix all node IDs to make it easier to avoid clash with hardcoded IDs.

This branch includes the following new commits:

     new 60987120774 CAMEL-18798/CAMEL-18771: Allow to configure nodePrefixId on route/routeTemplate to prefix all node IDs to make it easier to avoid clash with hardcoded IDs.
     new a1d9d5e4792 CAMEL-18798/CAMEL-18771: Allow to configure nodePrefixId on route/routeTemplate to prefix all node IDs to make it easier to avoid clash with hardcoded IDs.
     new 7f83712a446 CAMEL-18798/CAMEL-18771: Allow to configure nodePrefixId on route/routeTemplate to prefix all node IDs to make it easier to avoid clash with hardcoded IDs.
     new 9565e46ab61 CAMEL-18798/CAMEL-18771: Allow to configure nodePrefixId on route/routeTemplate to prefix all node IDs to make it easier to avoid clash with hardcoded IDs.

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



[camel] 01/04: CAMEL-18798/CAMEL-18771: Allow to configure nodePrefixId on route/routeTemplate to prefix all node IDs to make it easier to avoid clash with hardcoded IDs.

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

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

commit 6098712077423bedb8b5d21d7aaa3d9fb2516a27
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Dec 7 07:36:51 2022 +0100

    CAMEL-18798/CAMEL-18771: Allow to configure nodePrefixId on route/routeTemplate to prefix all node IDs to make it easier to avoid clash with hardcoded IDs.
---
 .../camel/catalog/models/templatedRoute.json       |   1 +
 .../main/java/org/apache/camel/CamelContext.java   |  35 ++++
 .../org/apache/camel/RouteTemplateContext.java     |   8 +-
 .../camel/impl/engine/SimpleCamelContext.java      |  13 ++
 .../org/apache/camel/impl/DefaultCamelContext.java |  24 ++-
 .../java/org/apache/camel/impl/DefaultModel.java   |  28 ++-
 .../camel/impl/lw/LightweightCamelContext.java     |  13 ++
 .../impl/lw/LightweightRuntimeCamelContext.java    |  13 ++
 .../org/apache/camel/model/templatedRoute.json     |   1 +
 .../camel/builder/TemplatedRouteBuilder.java       |  13 +-
 .../main/java/org/apache/camel/model/Model.java    |  30 ++++
 .../camel/model/OptionalIdentifiedDefinition.java  |  22 ++-
 .../apache/camel/model/ProcessorDefinition.java    |  17 ++
 .../org/apache/camel/model/RouteDefinition.java    |  29 +++
 .../apache/camel/model/RouteDefinitionHelper.java  |  38 ++++
 .../camel/model/TemplatedRouteDefinition.java      |  20 +++
 .../camel/builder/RouteTemplatePrefixIdTest.java   | 194 +++++++++++++++++++++
 .../processor/RouteNodePrefixIdDuplicateTest.java  |  58 ++++++
 .../camel/processor/RouteNodePrefixIdTest.java     |  70 ++++++++
 .../camel/main/MainTemplatedRoutePrefixIdTest.java |  77 ++++++++
 .../apache/camel/support/LocalBeanRegistry.java    |   6 +-
 .../java/org/apache/camel/xml/in/ModelParser.java  |   2 +
 22 files changed, 698 insertions(+), 14 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/templatedRoute.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/templatedRoute.json
index cfa496c210f..fe5d87f4f42 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/templatedRoute.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/templatedRoute.json
@@ -14,6 +14,7 @@
   "properties": {
     "routeTemplateRef": { "kind": "attribute", "displayName": "Route Template Ref", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the id of the route template to use to build the route." },
     "routeId": { "kind": "attribute", "displayName": "Route Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the id of the route built from the route template." },
+    "prefixId": { "kind": "attribute", "displayName": "Prefix Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets a prefix to use when assigning route and node IDs." },
     "parameter": { "kind": "element", "displayName": "Parameter", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.TemplatedRouteParameterDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Adds an input parameter of the template to build the route" },
     "bean": { "kind": "element", "displayName": "Bean", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.TemplatedRouteBeanDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Adds a local bean as input of the template to build the route" }
   }
diff --git a/core/camel-api/src/main/java/org/apache/camel/CamelContext.java b/core/camel-api/src/main/java/org/apache/camel/CamelContext.java
index fda8bccb99a..be6a61d50f8 100644
--- a/core/camel-api/src/main/java/org/apache/camel/CamelContext.java
+++ b/core/camel-api/src/main/java/org/apache/camel/CamelContext.java
@@ -635,6 +635,24 @@ public interface CamelContext extends CamelContextLifecycle, RuntimeConfiguratio
      */
     String addRouteFromTemplate(String routeId, String routeTemplateId, Map<String, Object> parameters) throws Exception;
 
+    /**
+     * Adds a new route from a given route template.
+     *
+     * Camel end users should favour using {@link org.apache.camel.builder.TemplatedRouteBuilder} which is a fluent
+     * builder with more functionality than this API.
+     *
+     * @param  routeId         the id of the new route to add (optional)
+     * @param  routeTemplateId the id of the route template (mandatory)
+     * @param  prefixId        prefix to use when assigning route and node IDs (optional)
+     * @param  parameters      parameters to use for the route template when creating the new route
+     * @return                 the id of the route added (for example when an id was auto assigned)
+     * @throws Exception       is thrown if error creating and adding the new route
+     */
+    String addRouteFromTemplate(
+            String routeId, String routeTemplateId, String prefixId,
+            Map<String, Object> parameters)
+            throws Exception;
+
     /**
      * Adds a new route from a given route template.
      *
@@ -650,6 +668,23 @@ public interface CamelContext extends CamelContextLifecycle, RuntimeConfiguratio
     String addRouteFromTemplate(String routeId, String routeTemplateId, RouteTemplateContext routeTemplateContext)
             throws Exception;
 
+    /**
+     * Adds a new route from a given route template.
+     *
+     * Camel end users should favour using {@link org.apache.camel.builder.TemplatedRouteBuilder} which is a fluent
+     * builder with more functionality than this API.
+     *
+     * @param  routeId              the id of the new route to add (optional)
+     * @param  routeTemplateId      the id of the route template (mandatory)
+     * @param  prefixId             prefix to use when assigning route and node IDs (optional)
+     * @param  routeTemplateContext the route template context (mandatory)
+     * @return                      the id of the route added (for example when an id was auto assigned)
+     * @throws Exception            is thrown if error creating and adding the new route
+     */
+    String addRouteFromTemplate(
+            String routeId, String routeTemplateId, String prefixId, RouteTemplateContext routeTemplateContext)
+            throws Exception;
+
     /**
      * Removes the route templates matching the pattern
      *
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 fa2c77dc2e7..855353d44b9 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
@@ -39,7 +39,7 @@ public interface RouteTemplateContext extends HasCamelContext {
     /**
      * Binds the bean to the repository (if possible).
      *
-     * If the bean is {@link CamelContextAware} then the registry will automatic inject the context if possible.
+     * If the bean is {@link CamelContextAware} then the registry will automatically inject the context if possible.
      *
      * @param id   the id of the bean
      * @param bean the bean
@@ -53,7 +53,7 @@ public interface RouteTemplateContext extends HasCamelContext {
      * <p/>
      * Binding by id and type allows to bind multiple entries with the same id but with different type.
      *
-     * If the bean is {@link CamelContextAware} then the registry will automatic inject the context if possible.
+     * If the bean is {@link CamelContextAware} then the registry will automatically inject the context if possible.
      *
      * @param id   the id of the bean
      * @param type the type of the bean to associate the binding
@@ -69,7 +69,7 @@ public interface RouteTemplateContext extends HasCamelContext {
      * <p/>
      * Binding by id and type allows to bind multiple entries with the same id but with different type.
      *
-     * If the bean is {@link CamelContextAware} then the registry will automatic inject the context if possible.
+     * If the bean is {@link CamelContextAware} then the registry will automatically inject the context if possible.
      *
      * @param id   the id of the bean
      * @param type the type of the bean to associate the binding
@@ -84,7 +84,7 @@ public interface RouteTemplateContext extends HasCamelContext {
      * <p/>
      * Binding by id and type allows to bind multiple entries with the same id but with different type.
      *
-     * If the bean is {@link CamelContextAware} then the registry will automatic inject the context if possible.
+     * If the bean is {@link CamelContextAware} then the registry will automatically inject the context if possible.
      *
      * @param id   the id of the bean
      * @param type the type of the bean to associate the binding
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java
index 690a87baced..16ddc3a347a 100644
--- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/SimpleCamelContext.java
@@ -704,12 +704,25 @@ public class SimpleCamelContext extends AbstractCamelContext {
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public String addRouteFromTemplate(String routeId, String routeTemplateId, String prefixId, Map<String, Object> parameters)
+            throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public String addRouteFromTemplate(String routeId, String routeTemplateId, RouteTemplateContext routeTemplateContext)
             throws Exception {
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public String addRouteFromTemplate(
+            String routeId, String routeTemplateId, String prefixId, RouteTemplateContext routeTemplateContext)
+            throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public void removeRouteTemplates(String pattern) throws Exception {
         throw new UnsupportedOperationException();
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 e9c2b30bc0e..54ab4d6ecdd 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
@@ -474,6 +474,15 @@ public class DefaultCamelContext extends SimpleCamelContext implements ModelCame
         return model.addRouteFromTemplate(routeId, routeTemplateId, parameters);
     }
 
+    @Override
+    public String addRouteFromTemplate(String routeId, String routeTemplateId, String prefixId, Map<String, Object> parameters)
+            throws Exception {
+        if (model == null && isLightweight()) {
+            throw new IllegalStateException("Access to model not supported in lightweight mode");
+        }
+        return model.addRouteFromTemplate(routeId, routeTemplateId, prefixId, parameters);
+    }
+
     @Override
     public String addRouteFromTemplate(String routeId, String routeTemplateId, RouteTemplateContext routeTemplateContext)
             throws Exception {
@@ -483,6 +492,16 @@ public class DefaultCamelContext extends SimpleCamelContext implements ModelCame
         return model.addRouteFromTemplate(routeId, routeTemplateId, routeTemplateContext);
     }
 
+    @Override
+    public String addRouteFromTemplate(
+            String routeId, String routeTemplateId, String prefixId, RouteTemplateContext routeTemplateContext)
+            throws Exception {
+        if (model == null && isLightweight()) {
+            throw new IllegalStateException("Access to model not supported in lightweight mode");
+        }
+        return model.addRouteFromTemplate(routeId, routeTemplateId, prefixId, routeTemplateContext);
+    }
+
     @Override
     public void addRouteFromTemplatedRoute(TemplatedRouteDefinition templatedRouteDefinition)
             throws Exception {
@@ -820,7 +839,8 @@ public class DefaultCamelContext extends SimpleCamelContext implements ModelCame
             List<RouteDefinition> routeDefinitionsToRemove = null;
             for (RouteDefinition routeDefinition : routeDefinitions) {
                 // assign ids to the routes and validate that the id's is all unique
-                String duplicate = RouteDefinitionHelper.validateUniqueIds(routeDefinition, routeDefinitions);
+                String duplicate = RouteDefinitionHelper.validateUniqueIds(routeDefinition, routeDefinitions,
+                        routeDefinition.getNodePrefixId());
                 if (duplicate != null) {
                     throw new FailedToStartRouteException(
                             routeDefinition.getId(),
@@ -900,6 +920,8 @@ public class DefaultCamelContext extends SimpleCamelContext implements ModelCame
 
                     // need to reset auto assigned ids, so there is no clash when creating routes
                     ProcessorDefinitionHelper.resetAllAutoAssignedNodeIds(routeDefinition);
+                    // must re-init parent when created from a template
+                    RouteDefinitionHelper.initParent(routeDefinition);
                 }
                 // Check if the route is included
                 if (includedRoute(routeDefinition)) {
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 48d8847cbff..2cac4a0903e 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
@@ -364,12 +364,30 @@ public class DefaultModel implements Model {
         if (parameters != null) {
             parameters.forEach(rtc::setParameter);
         }
-        return addRouteFromTemplate(routeId, routeTemplateId, rtc);
+        return addRouteFromTemplate(routeId, routeTemplateId, null, rtc);
     }
 
     @Override
+    public String addRouteFromTemplate(String routeId, String routeTemplateId, String prefixId, Map<String, Object> parameters)
+            throws Exception {
+        RouteTemplateContext rtc = new DefaultRouteTemplateContext(camelContext);
+        if (parameters != null) {
+            parameters.forEach(rtc::setParameter);
+        }
+        return addRouteFromTemplate(routeId, routeTemplateId, prefixId, rtc);
+    }
+
     public String addRouteFromTemplate(String routeId, String routeTemplateId, RouteTemplateContext routeTemplateContext)
             throws Exception {
+        return addRouteFromTemplate(routeId, routeTemplateId, null, routeTemplateContext);
+    }
+
+    @Override
+    public String addRouteFromTemplate(
+            String routeId, String routeTemplateId, String prefixId,
+            RouteTemplateContext routeTemplateContext)
+            throws Exception {
+
         RouteTemplateDefinition target = null;
         for (RouteTemplateDefinition def : routeTemplateDefinitions) {
             if (routeTemplateId.equals(def.getId())) {
@@ -459,6 +477,9 @@ public class DefaultModel implements Model {
         if (routeId != null) {
             def.setId(routeId);
         }
+        if (prefixId != null) {
+            def.setNodePrefixId(prefixId);
+        }
         def.setTemplateParameters(prop);
         def.setTemplateDefaultParameters(propDefaultValues);
         def.setRouteTemplateContext(routeTemplateContext);
@@ -473,7 +494,7 @@ public class DefaultModel implements Model {
         }
 
         // assign ids to the routes and validate that the id's are all unique
-        String duplicate = RouteDefinitionHelper.validateUniqueIds(def, routeDefinitions);
+        String duplicate = RouteDefinitionHelper.validateUniqueIds(def, routeDefinitions, prefixId);
         if (duplicate != null) {
             throw new FailedToCreateRouteFromTemplateException(
                     routeId, routeTemplateId,
@@ -740,6 +761,7 @@ public class DefaultModel implements Model {
     public void addRouteFromTemplatedRoute(TemplatedRouteDefinition templatedRouteDefinition)
             throws Exception {
         ObjectHelper.notNull(templatedRouteDefinition, "templatedRouteDefinition");
+
         final RouteTemplateContext routeTemplateContext = new DefaultRouteTemplateContext(camelContext);
         // Load the parameters into the context
         final List<TemplatedRouteParameterDefinition> parameters = templatedRouteDefinition.getParameters();
@@ -757,7 +779,7 @@ public class DefaultModel implements Model {
         }
         // Add the route
         addRouteFromTemplate(templatedRouteDefinition.getRouteId(), templatedRouteDefinition.getRouteTemplateRef(),
-                routeTemplateContext);
+                templatedRouteDefinition.getPrefixId(), routeTemplateContext);
     }
 
     @Override
diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java
index 0800e42775e..7fc6ace53ac 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java
@@ -1961,12 +1961,25 @@ public class LightweightCamelContext implements ExtendedCamelContext, CatalogCam
         return getModelCamelContext().addRouteFromTemplate(routeId, routeTemplateId, parameters);
     }
 
+    @Override
+    public String addRouteFromTemplate(String routeId, String routeTemplateId, String prefixId, Map<String, Object> parameters)
+            throws Exception {
+        return getModelCamelContext().addRouteFromTemplate(routeId, routeTemplateId, prefixId, parameters);
+    }
+
     @Override
     public String addRouteFromTemplate(String routeId, String routeTemplateId, RouteTemplateContext routeTemplateContext)
             throws Exception {
         return getModelCamelContext().addRouteFromTemplate(routeId, routeTemplateId, routeTemplateContext);
     }
 
+    @Override
+    public String addRouteFromTemplate(
+            String routeId, String routeTemplateId, String prefixId, RouteTemplateContext routeTemplateContext)
+            throws Exception {
+        return getModelCamelContext().addRouteFromTemplate(routeId, routeTemplateId, prefixId, routeTemplateContext);
+    }
+
     @Override
     public void addRouteFromTemplatedRoute(TemplatedRouteDefinition templatedRouteDefinition)
             throws Exception {
diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
index aec0eda6796..122a6237168 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
@@ -2109,12 +2109,25 @@ public class LightweightRuntimeCamelContext implements ExtendedCamelContext, Cat
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public String addRouteFromTemplate(String routeId, String routeTemplateId, String prefixId, Map<String, Object> parameters)
+            throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public String addRouteFromTemplate(String routeId, String routeTemplateId, RouteTemplateContext routeTemplateContext)
             throws Exception {
         throw new UnsupportedOperationException();
     }
 
+    @Override
+    public String addRouteFromTemplate(
+            String routeId, String routeTemplateId, String prefixId, RouteTemplateContext routeTemplateContext)
+            throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public void removeRouteTemplates(String pattern) throws Exception {
         throw new UnsupportedOperationException();
diff --git a/core/camel-core-model/src/generated/resources/org/apache/camel/model/templatedRoute.json b/core/camel-core-model/src/generated/resources/org/apache/camel/model/templatedRoute.json
index cfa496c210f..fe5d87f4f42 100644
--- a/core/camel-core-model/src/generated/resources/org/apache/camel/model/templatedRoute.json
+++ b/core/camel-core-model/src/generated/resources/org/apache/camel/model/templatedRoute.json
@@ -14,6 +14,7 @@
   "properties": {
     "routeTemplateRef": { "kind": "attribute", "displayName": "Route Template Ref", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the id of the route template to use to build the route." },
     "routeId": { "kind": "attribute", "displayName": "Route Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the id of the route built from the route template." },
+    "prefixId": { "kind": "attribute", "displayName": "Prefix Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets a prefix to use when assigning route and node IDs." },
     "parameter": { "kind": "element", "displayName": "Parameter", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.TemplatedRouteParameterDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Adds an input parameter of the template to build the route" },
     "bean": { "kind": "element", "displayName": "Bean", "required": false, "type": "array", "javaType": "java.util.List<org.apache.camel.model.TemplatedRouteBeanDefinition>", "deprecated": false, "autowired": false, "secret": false, "description": "Adds a local bean as input of the template to build the route" }
   }
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 9f5bd6a484f..09cd5de0941 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
@@ -36,6 +36,7 @@ public final class TemplatedRouteBuilder {
     private final String routeTemplateId;
     private final RouteTemplateContext routeTemplateContext;
     private String routeId;
+    private String prefixId;
     private Consumer<RouteTemplateDefinition> handler;
     private Consumer<RouteTemplateContext> configurer;
 
@@ -67,6 +68,16 @@ public final class TemplatedRouteBuilder {
         return this;
     }
 
+    /**
+     * Sets a prefix to use when assigning route and node IDs.
+     *
+     * @param prefixId the prefix id
+     */
+    public TemplatedRouteBuilder prefixId(String prefixId) {
+        this.prefixId = prefixId;
+        return this;
+    }
+
     /**
      * Adds a parameter the route template will use when creating the route.
      *
@@ -165,7 +176,7 @@ public final class TemplatedRouteBuilder {
             if (configurer != null) {
                 routeTemplateContext.setConfigurer(configurer);
             }
-            return camelContext.addRouteFromTemplate(routeId, routeTemplateId, routeTemplateContext);
+            return camelContext.addRouteFromTemplate(routeId, routeTemplateId, prefixId, routeTemplateContext);
         } catch (Exception e) {
             throw RuntimeCamelException.wrapRuntimeException(e);
         }
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/Model.java b/core/camel-core-model/src/main/java/org/apache/camel/model/Model.java
index ae3528bd3de..a3148f46153 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/Model.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/Model.java
@@ -223,6 +223,21 @@ public interface Model {
      */
     String addRouteFromTemplate(String routeId, String routeTemplateId, Map<String, Object> parameters) throws Exception;
 
+    /**
+     * Adds a new route from a given route template
+     *
+     * @param  routeId         the id of the new route to add (optional)
+     * @param  routeTemplateId the id of the route template (mandatory)
+     * @param  prefixId        prefix to use when assigning route and node IDs (optional)
+     * @param  parameters      parameters to use for the route template when creating the new route
+     * @return                 the id of the route added (for example when an id was auto assigned)
+     * @throws Exception       is thrown if error creating and adding the new route
+     */
+    String addRouteFromTemplate(
+            String routeId, String routeTemplateId, String prefixId,
+            Map<String, Object> parameters)
+            throws Exception;
+
     /**
      * Adds a new route from a given route template
      *
@@ -235,6 +250,21 @@ public interface Model {
     String addRouteFromTemplate(String routeId, String routeTemplateId, RouteTemplateContext routeTemplateContext)
             throws Exception;
 
+    /**
+     * Adds a new route from a given route template
+     *
+     * @param  routeId              the id of the new route to add (optional)
+     * @param  routeTemplateId      the id of the route template (mandatory)
+     * @param  prefixId             prefix to use when assigning route and node IDs (optional)
+     * @param  routeTemplateContext the route template context (mandatory)
+     * @return                      the id of the route added (for example when an id was auto assigned)
+     * @throws Exception            is thrown if error creating and adding the new route
+     */
+    String addRouteFromTemplate(
+            String routeId, String routeTemplateId, String prefixId,
+            RouteTemplateContext routeTemplateContext)
+            throws Exception;
+
     /**
      * Adds a new route from a given templated route definition
      *
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/OptionalIdentifiedDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/OptionalIdentifiedDefinition.java
index f1df499f8b0..7cc89556f6d 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/OptionalIdentifiedDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/OptionalIdentifiedDefinition.java
@@ -43,8 +43,8 @@ public abstract class OptionalIdentifiedDefinition<T extends OptionalIdentifiedD
     private String id;
     private Boolean customId;
     private DescriptionDefinition description;
-    private transient int lineNumber = -1;
-    private transient String location;
+    private int lineNumber = -1;
+    private String location;
 
     @Override
     public CamelContext getCamelContext() {
@@ -188,10 +188,26 @@ public abstract class OptionalIdentifiedDefinition<T extends OptionalIdentifiedD
      * Gets the node id, creating one if not already set.
      */
     public String idOrCreate(NodeIdFactory factory) {
+        // prefix is only for nodes in the route (not the route id)
+        String prefix = null;
+        boolean iAmRoute = this instanceof RouteDefinition;
+        boolean allowPrefix = !iAmRoute && this instanceof ProcessorDefinition;
+        if (allowPrefix) {
+            RouteDefinition route = ProcessorDefinitionHelper.getRoute(this);
+            if (route != null) {
+                prefix = route.getNodePrefixId();
+            }
+        }
         if (id == null) {
             setGeneratedId(factory.createId(this));
         }
-        return id;
+
+        // return with prefix
+        if (prefix != null) {
+            return prefix + id;
+        } else {
+            return id;
+        }
     }
 
     public Boolean getCustomId() {
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
index 0f9adf65346..23846c0a94d 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
@@ -865,6 +865,23 @@ public abstract class ProcessorDefinition<Type extends ProcessorDefinition<Type>
         return asType();
     }
 
+    /**
+     * Prefix to use for node IDs (both set and auto-assigned IDs)
+     *
+     * @param  prefixId the prefix
+     * @return          the builder
+     */
+    public Type nodePrefixId(String prefixId) {
+        ProcessorDefinition<?> def = this;
+
+        RouteDefinition route = ProcessorDefinitionHelper.getRoute(def);
+        if (route != null) {
+            route.setNodePrefixId(prefixId);
+        }
+
+        return asType();
+    }
+
     /**
      * Disables this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later
      * at runtime.
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java
index 9f591c6205a..48fe5496bef 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java
@@ -66,6 +66,7 @@ public class RouteDefinition extends OutputDefinition<RouteDefinition>
     private String routeConfigurationId;
     private transient Set<String> appliedRouteConfigurationIds;
     private String group;
+    private String nodePrefixId;
     private String streamCache;
     private String trace;
     private String messageHistory;
@@ -280,6 +281,18 @@ public class RouteDefinition extends OutputDefinition<RouteDefinition>
         return this;
     }
 
+    /**
+     * Prefix to use for node IDs (both set and auto-assigned IDs)
+     *
+     * @param  prefixId the prefix
+     * @return          the builder
+     */
+    @Override
+    public RouteDefinition nodePrefixId(String prefixId) {
+        setNodePrefixId(prefixId);
+        return this;
+    }
+
     /**
      * Disable stream caching for this route.
      *
@@ -855,6 +868,22 @@ public class RouteDefinition extends OutputDefinition<RouteDefinition>
         this.group = group;
     }
 
+    /**
+     * Prefix to use for route and node IDs (also IDs that has been explicit set to a value)
+     */
+    public String getNodePrefixId() {
+        return nodePrefixId;
+    }
+
+    /**
+     * Prefix to use for route and node IDs (also IDs that has been explicit set to a value)
+     */
+    @XmlAttribute
+    @Metadata(label = "advanced")
+    public void setNodePrefixId(String nodePrefixId) {
+        this.nodePrefixId = nodePrefixId;
+    }
+
     /**
      * Whether stream caching is enabled on this route.
      */
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java
index 0e27dc6af8a..2b07b5bb86a 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java
@@ -245,6 +245,18 @@ public final class RouteDefinitionHelper {
      * @return        <tt>null</tt> if no duplicate id's detected, otherwise the first found duplicate id is returned.
      */
     public static String validateUniqueIds(RouteDefinition target, List<RouteDefinition> routes) {
+        return validateUniqueIds(target, routes, null);
+    }
+
+    /**
+     * Validates that the target route has no duplicate id's from any of the existing routes.
+     *
+     * @param  target   the target route
+     * @param  routes   the existing routes
+     * @param  prefixId optional prefix to use in duplicate id detection
+     * @return          <tt>null</tt> if no duplicate id's detected, otherwise the first found duplicate id is returned.
+     */
+    public static String validateUniqueIds(RouteDefinition target, List<RouteDefinition> routes, String prefixId) {
         Set<String> routesIds = new LinkedHashSet<>();
         // gather all ids for the existing route, but only include custom ids,
         // and no abstract ids
@@ -267,6 +279,9 @@ public final class RouteDefinitionHelper {
 
         // now check for clash with the target route
         for (String id : targetIds) {
+            if (prefixId != null) {
+                id = prefixId + id;
+            }
             if (routesIds.contains(id)) {
                 return id;
             }
@@ -739,6 +754,29 @@ public final class RouteDefinitionHelper {
         }
     }
 
+    /**
+     * For all custom assigned ids, then to avoid duplicate ids when creating new routes from route templates, then the
+     * custom assigned ids, must be prefixed.
+     *
+     * @param context   the camel context
+     * @param prefix    the prefix to set on custom assigned id
+     * @param processor the node
+     */
+    public static void prefixCustomAssignIds(CamelContext context, final String prefix, final ProcessorDefinition processor) {
+        if (processor.hasCustomIdAssigned()) {
+            String originalId = processor.getId();
+            String newId = prefix + originalId;
+            processor.setId(newId);
+        }
+
+        List<ProcessorDefinition<?>> children = processor.getOutputs();
+        if (children != null && !children.isEmpty()) {
+            for (ProcessorDefinition child : children) {
+                prefixCustomAssignIds(context, prefix, child);
+            }
+        }
+    }
+
     public static String getRouteMessage(String route) {
         // cut the route after 60 chars, so it won't be too big in the message
         // users just need to be able to identify the route, so they know where to look
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/TemplatedRouteDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/TemplatedRouteDefinition.java
index 98da50a8c9b..5db5ae8942b 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/TemplatedRouteDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/TemplatedRouteDefinition.java
@@ -50,6 +50,8 @@ public class TemplatedRouteDefinition implements CamelContextAware {
     private String routeTemplateRef;
     @XmlAttribute
     private String routeId;
+    @XmlAttribute
+    private String prefixId;
     @XmlElement(name = "parameter")
     @Metadata(description = "Adds an input parameter of the template to build the route")
     private List<TemplatedRouteParameterDefinition> parameters;
@@ -89,6 +91,14 @@ public class TemplatedRouteDefinition implements CamelContextAware {
         this.routeId = routeId;
     }
 
+    public String getPrefixId() {
+        return prefixId;
+    }
+
+    public void setPrefixId(String prefixId) {
+        this.prefixId = prefixId;
+    }
+
     @Override
     public CamelContext getCamelContext() {
         return camelContext;
@@ -260,6 +270,16 @@ public class TemplatedRouteDefinition implements CamelContextAware {
         return def;
     }
 
+    /**
+     * Sets a prefix to use when assigning route and node IDs.
+     *
+     * @param id the prefix id
+     */
+    public TemplatedRouteDefinition prefixId(String id) {
+        setPrefixId(id);
+        return this;
+    }
+
     /**
      * Sets the id of the route built from the route template.
      *
diff --git a/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplatePrefixIdTest.java b/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplatePrefixIdTest.java
new file mode 100644
index 00000000000..eadba72450a
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplatePrefixIdTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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 java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Route;
+import org.apache.camel.model.RouteTemplateDefinition;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class RouteTemplatePrefixIdTest extends ContextTestSupport {
+
+    // TODO: docs
+    // TODO: <xml> tests
+    // TODO: regen
+
+    @Test
+    public void testDefineRouteTemplate() throws Exception {
+        assertEquals(1, context.getRouteTemplateDefinitions().size());
+
+        RouteTemplateDefinition routeTemplate = context.getRouteTemplateDefinition("myTemplate");
+        assertEquals("foo", routeTemplate.getTemplateParameters().get(0).getName());
+        assertEquals("bar", routeTemplate.getTemplateParameters().get(1).getName());
+    }
+
+    @Test
+    public void testCreateRouteFromRouteTemplate() throws Exception {
+        assertEquals(1, context.getRouteTemplateDefinitions().size());
+
+        RouteTemplateDefinition routeTemplate = context.getRouteTemplateDefinition("myTemplate");
+        assertEquals("foo", routeTemplate.getTemplateParameters().get(0).getName());
+        assertEquals("bar", routeTemplate.getTemplateParameters().get(1).getName());
+
+        getMockEndpoint("mock:cheese").expectedBodiesReceived("Hello Cheese");
+        getMockEndpoint("mock:cake").expectedBodiesReceived("Hello Cake");
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("foo", "one");
+        parameters.put("bar", "cheese");
+        context.addRouteFromTemplate("first", "myTemplate", "aaa", parameters);
+
+        parameters.put("foo", "two");
+        parameters.put("bar", "cake");
+        context.addRouteFromTemplate("second", "myTemplate", "bbb", parameters);
+
+        assertEquals(2, context.getRouteDefinitions().size());
+        assertEquals(2, context.getRoutes().size());
+        assertEquals("Started", context.getRouteController().getRouteStatus("first").name());
+        assertEquals("Started", context.getRouteController().getRouteStatus("second").name());
+        assertEquals("true", context.getRoute("first").getProperties().get(Route.TEMPLATE_PROPERTY));
+        assertEquals("true", context.getRoute("second").getProperties().get(Route.TEMPLATE_PROPERTY));
+
+        template.sendBody("direct:one", "Hello Cheese");
+        template.sendBody("direct:two", "Hello Cake");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testCreateRouteFromRouteTemplateFluent() throws Exception {
+        assertEquals(1, context.getRouteTemplateDefinitions().size());
+
+        RouteTemplateDefinition routeTemplate = context.getRouteTemplateDefinition("myTemplate");
+        assertEquals("foo", routeTemplate.getTemplateParameters().get(0).getName());
+        assertEquals("bar", routeTemplate.getTemplateParameters().get(1).getName());
+
+        getMockEndpoint("mock:cheese").expectedBodiesReceived("Hello Cheese");
+        getMockEndpoint("mock:cake").expectedBodiesReceived("Hello Cake");
+
+        TemplatedRouteBuilder.builder(context, "myTemplate")
+                .routeId("first")
+                .prefixId("aaa")
+                .parameter("foo", "one")
+                .parameter("bar", "cheese")
+                .add();
+
+        TemplatedRouteBuilder.builder(context, "myTemplate")
+                .routeId("second")
+                .prefixId("bbb")
+                .parameter("foo", "two")
+                .parameter("bar", "cake")
+                .add();
+
+        assertEquals(2, context.getRouteDefinitions().size());
+        assertEquals(2, context.getRoutes().size());
+        assertEquals("Started", context.getRouteController().getRouteStatus("first").name());
+        assertEquals("Started", context.getRouteController().getRouteStatus("second").name());
+        assertEquals("true", context.getRoute("first").getProperties().get(Route.TEMPLATE_PROPERTY));
+        assertEquals("true", context.getRoute("second").getProperties().get(Route.TEMPLATE_PROPERTY));
+
+        template.sendBody("direct:one", "Hello Cheese");
+        template.sendBody("direct:two", "Hello Cake");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testCreateRouteFromRouteTemplateAutoAssignedRouteId() throws Exception {
+        assertEquals(1, context.getRouteTemplateDefinitions().size());
+
+        RouteTemplateDefinition routeTemplate = context.getRouteTemplateDefinition("myTemplate");
+        assertEquals("foo", routeTemplate.getTemplateParameters().get(0).getName());
+        assertEquals("bar", routeTemplate.getTemplateParameters().get(1).getName());
+
+        getMockEndpoint("mock:cheese").expectedBodiesReceived("Hello Cheese");
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("foo", "one");
+        parameters.put("bar", "cheese");
+        String routeId = context.addRouteFromTemplate(null, "myTemplate", "aaa", parameters);
+
+        assertNotNull(routeId);
+        assertEquals(1, context.getRouteDefinitions().size());
+        assertEquals(1, context.getRoutes().size());
+        assertEquals("Started", context.getRouteController().getRouteStatus(routeId).name());
+        assertEquals("true", context.getRoute(routeId).getProperties().get(Route.TEMPLATE_PROPERTY));
+
+        template.sendBody("direct:one", "Hello Cheese");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testCreateRouteFromRouteTemplateAutoAssignedRouteIdClash() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                // use a route id that can clash with auto assigned
+                from("direct:hello").to("mock:hello").routeId("route1");
+            }
+        });
+
+        assertEquals(1, context.getRouteDefinitions().size());
+        assertEquals(1, context.getRouteTemplateDefinitions().size());
+
+        RouteTemplateDefinition routeTemplate = context.getRouteTemplateDefinition("myTemplate");
+        assertEquals("foo", routeTemplate.getTemplateParameters().get(0).getName());
+        assertEquals("bar", routeTemplate.getTemplateParameters().get(1).getName());
+
+        getMockEndpoint("mock:cheese").expectedBodiesReceived("Hello Cheese");
+
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("foo", "one");
+        parameters.put("bar", "cheese");
+        String routeId = context.addRouteFromTemplate(null, "myTemplate", "aaa", parameters);
+
+        assertNotNull(routeId);
+        assertNotEquals("route1", routeId, "Should not be named route1");
+        assertEquals(2, context.getRouteDefinitions().size());
+        assertEquals(2, context.getRoutes().size());
+        assertEquals("Started", context.getRouteController().getRouteStatus(routeId).name());
+        assertEquals("true", context.getRoute(routeId).getProperties().get(Route.TEMPLATE_PROPERTY));
+
+        template.sendBody("direct:one", "Hello Cheese");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                routeTemplate("myTemplate").templateParameter("foo").templateParameter("bar")
+                        .from("direct:{{foo}}")
+                        .choice()
+                        .when(header("foo"))
+                        .log("${body}").id("myLog")
+                        .otherwise()
+                        .to("mock:{{bar}}").id("end");
+            }
+        };
+    }
+}
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/RouteNodePrefixIdDuplicateTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/RouteNodePrefixIdDuplicateTest.java
new file mode 100644
index 00000000000..28796d46c7d
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/RouteNodePrefixIdDuplicateTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.processor;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class RouteNodePrefixIdDuplicateTest extends ContextTestSupport {
+
+    @Test
+    public void testRoutePrefixId() throws Exception {
+        Assertions.assertEquals(2, context.getRoutes().size());
+
+        // ID should be prefixed
+        SendProcessor send = context.getProcessor("myMock", SendProcessor.class);
+        Assertions.assertNull(send);
+        send = context.getProcessor("aaamyMock", SendProcessor.class);
+        Assertions.assertNotNull(send);
+        send = context.getProcessor("bbbmyMock", SendProcessor.class);
+        Assertions.assertNotNull(send);
+
+        // all nodes should include prefix
+        Assertions.assertEquals(2, context.getRoute("foo").filter("aaa*").size());
+        Assertions.assertEquals(2, context.getRoute("bar").filter("bbb*").size());
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:foo").routeId("foo").nodePrefixId("aaa")
+                        .to("mock:foo").id("myMock")
+                        .to("seda:foo");
+
+                from("direct:bar").nodePrefixId("bbb").routeId("bar")
+                        .to("mock:bar").id("myMock")
+                        .to("seda:bar");
+            }
+        };
+    }
+}
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/RouteNodePrefixIdTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/RouteNodePrefixIdTest.java
new file mode 100644
index 00000000000..61e8c5216a3
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/RouteNodePrefixIdTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.processor;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class RouteNodePrefixIdTest extends ContextTestSupport {
+
+    @Test
+    public void testRoutePrefixId() throws Exception {
+        Assertions.assertEquals(3, context.getRoutes().size());
+
+        // ID should be prefixed
+        SendProcessor send = context.getProcessor("aaamyFoo", SendProcessor.class);
+        Assertions.assertNotNull(send);
+        send = context.getProcessor("bbbmyBar", SendProcessor.class);
+        Assertions.assertNotNull(send);
+        send = context.getProcessor("cccmyCheese", SendProcessor.class);
+        Assertions.assertNotNull(send);
+
+        // all nodes should include prefix
+        Assertions.assertEquals(2, context.getRoute("foo").filter("aaa*").size());
+        Assertions.assertEquals(2, context.getRoute("bar").filter("bbb*").size());
+        Assertions.assertEquals(3, context.getRoutes().get(2).filter("ccc*").size());
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:foo").routeId("foo").nodePrefixId("aaa")
+                        .to("mock:foo").id("myFoo")
+                        .to("seda:foo");
+
+                from("direct:bar").nodePrefixId("bbb").routeId("bar")
+                        .to("mock:bar").id("myBar")
+                        .to("seda:bar");
+
+                from("direct:cheese")
+                        .nodePrefixId("ccc")
+                        .choice()
+                            .when(header("cheese"))
+                                .to("mock:cheese").id("myCheese")
+                            .otherwise()
+                                .to("mock:gauda")
+                            .end();
+            }
+        };
+    }
+}
diff --git a/core/camel-main/src/test/java/org/apache/camel/main/MainTemplatedRoutePrefixIdTest.java b/core/camel-main/src/test/java/org/apache/camel/main/MainTemplatedRoutePrefixIdTest.java
new file mode 100644
index 00000000000..1d5e9e8686b
--- /dev/null
+++ b/core/camel-main/src/test/java/org/apache/camel/main/MainTemplatedRoutePrefixIdTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.main;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.processor.LogProcessor;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class MainTemplatedRoutePrefixIdTest {
+
+    @Test
+    void testMain() throws Exception {
+        Main main = new Main();
+        main.configure().addRoutesBuilder(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                routeTemplate("myTemplate")
+                        .templateParameter("foo")
+                        .templateParameter("bar")
+                        .from("direct:{{foo}}")
+                        .choice()
+                        .when(header("foo"))
+                        .log("${body}").id("myLog")
+                        .otherwise()
+                        .to("mock:{{bar}}").id("end");
+
+                templatedRoute("myTemplate")
+                        .routeId("my-route")
+                        .prefixId("aaa")
+                        .parameter("foo", "fooVal")
+                        .parameter("bar", "barVal");
+
+                templatedRoute("myTemplate")
+                        .routeId("my-route2")
+                        .prefixId("bbb")
+                        .parameter("foo", "fooVal2")
+                        .parameter("bar", "barVal2");
+            }
+        });
+
+        main.start();
+
+        CamelContext context = main.getCamelContext();
+        assertEquals(2, context.getRoutes().size());
+
+        // ID should be prefixed
+        LogProcessor log = context.getProcessor("aaamyLog", LogProcessor.class);
+        Assertions.assertNotNull(log);
+        log = context.getProcessor("bbbmyLog", LogProcessor.class);
+        Assertions.assertNotNull(log);
+
+        // all nodes should include prefix
+        Assertions.assertEquals(3, context.getRoute("my-route").filter("aaa*").size());
+        Assertions.assertEquals(3, context.getRoute("my-route2").filter("bbb*").size());
+
+        main.stop();
+    }
+
+}
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/LocalBeanRegistry.java b/core/camel-support/src/main/java/org/apache/camel/support/LocalBeanRegistry.java
index 08426c6ca9f..d26845f611a 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/LocalBeanRegistry.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/LocalBeanRegistry.java
@@ -48,9 +48,11 @@ public final class LocalBeanRegistry extends SupplierRegistry {
      * registry or endpoint registry in Camel. Then there is a check that validates for clashes and then re-assign key
      * names.
      *
-     * @param oldKey the old key name
-     * @param newKey the new key name
+     * @param      oldKey the old key name
+     * @param      newKey the new key name
+     * @deprecated        not in use
      */
+    @Deprecated
     public void swapKey(String oldKey, String newKey) {
         Map<Class<?>, Object> value = remove(oldKey);
         if (value != null) {
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 f4aaa921674..297e9f235fe 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
@@ -1043,6 +1043,7 @@ public class ModelParser extends BaseParser {
                 case "group": def.setGroup(val); break;
                 case "logMask": def.setLogMask(val); break;
                 case "messageHistory": def.setMessageHistory(val); break;
+                case "nodePrefixId": def.setNodePrefixId(val); break;
                 case "precondition": def.setPrecondition(val); break;
                 case "routeConfigurationId": def.setRouteConfigurationId(val); break;
                 case "routePolicyRef": def.setRoutePolicyRef(val); break;
@@ -1363,6 +1364,7 @@ public class ModelParser extends BaseParser {
     protected TemplatedRouteDefinition doParseTemplatedRouteDefinition() throws IOException, XmlPullParserException {
         return doParse(new TemplatedRouteDefinition(), (def, key, val) -> {
             switch (key) {
+                case "prefixId": def.setPrefixId(val); break;
                 case "routeId": def.setRouteId(val); break;
                 case "routeTemplateRef": def.setRouteTemplateRef(val); break;
                 default: return false;


[camel] 04/04: CAMEL-18798/CAMEL-18771: Allow to configure nodePrefixId on route/routeTemplate to prefix all node IDs to make it easier to avoid clash with hardcoded IDs.

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

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

commit 9565e46ab61b66e3cfada2cda25da3c44cc66211
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Dec 7 09:34:36 2022 +0100

    CAMEL-18798/CAMEL-18771: Allow to configure nodePrefixId on route/routeTemplate to prefix all node IDs to make it easier to avoid clash with hardcoded IDs.
---
 .../modules/ROOT/pages/route-template.adoc         | 83 ++++++++++++++++++++++
 1 file changed, 83 insertions(+)

diff --git a/docs/user-manual/modules/ROOT/pages/route-template.adoc b/docs/user-manual/modules/ROOT/pages/route-template.adoc
index ccc86207b49..8b08497f9a6 100644
--- a/docs/user-manual/modules/ROOT/pages/route-template.adoc
+++ b/docs/user-manual/modules/ROOT/pages/route-template.adoc
@@ -254,6 +254,89 @@ And in YAML DSL
         value: "5s"
 ----
 
+=== Using hardcoded node IDs in route templates
+
+If route templates contains hardcoded node IDs then routes created from templates will use the same IDs
+and therefore if 2 or more routes are created from the same template, you will have _duplicate id detected_ error.
+
+Given the route template below, then it has hardcoded ID (_new-order_) in node calling the http services.
+
+[source,java]
+----
+public class MyRouteTemplates extends RouteBuilder {
+
+    @Override
+    public void configure() throws Exception {
+        routeTemplate("orderTemplate")
+            .templateParameter("queue")
+            .from("jms:{{queue}}")
+                .to("http:orderserver.acme.com/neworder").id("new-order")
+                .log("Processing order");
+    }
+}
+----
+
+When creating routes from templates, you can then provide a _prefix_ which are used for all node IDs.
+This allows to create 2 or more routes without _duplicate id_ errors.
+
+For example in the following we create a new route _myCoolRoute_ from the _myTemplate_ template, and
+use a prefix of _web_.
+
+And in Java DSL
+
+[source,java]
+----
+templatedRoute("orderTemplate")
+        .routeId("webOrder")
+        .prefixId("web")
+        .parameter("queue", "order.web");
+----
+
+Then we can create a 2nd route:
+
+[source,java]
+----
+templatedRoute("orderTemplate")
+        .routeId("ftpOrder")
+        .prefixId("ftp")
+        .parameter("queue", "order.ftp");
+----
+
+And in Spring XML DSL
+
+[source,xml]
+----
+<camelContext>
+  <templatedRoute routeTemplateRef="orderTemplate" routeId="webOrder" prefixId="web">
+    <parameter name="queue" value="web"/>
+  </templatedRoute>
+</camelContext>
+----
+
+And in XML DSL
+
+[source,xml]
+----
+<templatedRoutes xmlns="http://camel.apache.org/schema/spring">
+  <templatedRoute routeTemplateRef="orderTemplate" routeId="webOrder" prefixId="web">
+    <parameter name="queue" value="web"/>
+  </templatedRoute>
+</templatedRoutes>
+----
+
+And in YAML DSL
+
+[source,yaml]
+----
+- templated-route:
+    route-template-ref: "orderTemplate"
+    route-id: "webOrder"
+    prefix-id: "web"
+    parameters:
+      - name: "queue"
+        value: "web"
+----
+
 == Binding beans to route template
 
 The route template allows to bind beans which is local scoped and only used as part of creating routes from the template.


[camel] 03/04: CAMEL-18798/CAMEL-18771: Allow to configure nodePrefixId on route/routeTemplate to prefix all node IDs to make it easier to avoid clash with hardcoded IDs.

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

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

commit 7f83712a44613514c1093c5ee5658d2a3b84eae4
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Dec 7 08:57:49 2022 +0100

    CAMEL-18798/CAMEL-18771: Allow to configure nodePrefixId on route/routeTemplate to prefix all node IDs to make it easier to avoid clash with hardcoded IDs.
---
 .../deserializers/RouteDefinitionDeserializer.java |  5 +++
 .../TemplatedRouteDefinitionDeserializer.java      |  6 ++++
 .../generated/resources/schema/camel-yaml-dsl.json |  6 ++++
 .../generated/resources/schema/camelYamlDsl.json   |  6 ++++
 .../apache/camel/dsl/yaml/RouteTemplateTest.groovy | 40 ++++++++++++++++++++++
 .../org/apache/camel/dsl/yaml/RoutesTest.groovy    | 33 ++++++++++++++++++
 6 files changed, 96 insertions(+)

diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/RouteDefinitionDeserializer.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/RouteDefinitionDeserializer.java
index eaec780238d..08072f756f3 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/RouteDefinitionDeserializer.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/RouteDefinitionDeserializer.java
@@ -39,6 +39,7 @@ import org.snakeyaml.engine.v2.nodes.NodeTuple;
                   @YamlProperty(name = "id", type = "string"),
                   @YamlProperty(name = "description", type = "string"),
                   @YamlProperty(name = "group", type = "string"),
+                  @YamlProperty(name = "node-prefix-id", type = "string"),
                   @YamlProperty(name = "precondition", type = "string"),
                   @YamlProperty(name = "route-configuration-id", type = "string"),
                   @YamlProperty(name = "auto-startup", type = "boolean"),
@@ -84,6 +85,10 @@ public class RouteDefinitionDeserializer extends YamlDeserializerBase<RouteDefin
                 case "group":
                     target.setGroup(asText(val));
                     break;
+                case "nodePrefixId":
+                case "node-prefix-id":
+                    target.setNodePrefixId(asText(val));
+                    break;
                 case "routeConfigurationId":
                 case "route-configuration-id":
                     target.setRouteConfigurationId(asText(val));
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/TemplatedRouteDefinitionDeserializer.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/TemplatedRouteDefinitionDeserializer.java
index e088c47c451..fd9f8747ffe 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/TemplatedRouteDefinitionDeserializer.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/main/java/org/apache/camel/dsl/yaml/deserializers/TemplatedRouteDefinitionDeserializer.java
@@ -35,6 +35,8 @@ import org.snakeyaml.engine.v2.nodes.Node;
           properties = {
                   @YamlProperty(name = "route-id",
                                 type = "string"),
+                  @YamlProperty(name = "prefix-id",
+                                type = "string"),
                   @YamlProperty(name = "route-template-ref",
                                 type = "string",
                                 required = true),
@@ -63,6 +65,10 @@ public class TemplatedRouteDefinitionDeserializer extends YamlDeserializerBase<T
                 target.setRouteId(asText(node));
                 break;
             }
+            case "prefixId":
+            case "prefix-id":
+                target.setPrefixId(asText(node));
+                break;
             case "routeTemplateRef":
             case "route-template-ref": {
                 target.setRouteTemplateRef(asText(node));
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camel-yaml-dsl.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camel-yaml-dsl.json
index 960fa3c8b7f..68cc7020f88 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camel-yaml-dsl.json
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camel-yaml-dsl.json
@@ -2565,6 +2565,9 @@
           "message-history" : {
             "type" : "boolean"
           },
+          "node-prefix-id" : {
+            "type" : "string"
+          },
           "precondition" : {
             "type" : "string"
           },
@@ -3097,6 +3100,9 @@
               "$ref" : "#/items/definitions/org.apache.camel.model.TemplatedRouteParameterDefinition"
             }
           },
+          "prefix-id" : {
+            "type" : "string"
+          },
           "route-id" : {
             "type" : "string"
           },
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
index d98ccef879d..13b4e3c94a1 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
@@ -2469,6 +2469,9 @@
           "messageHistory" : {
             "type" : "boolean"
           },
+          "nodePrefixId" : {
+            "type" : "string"
+          },
           "precondition" : {
             "type" : "string"
           },
@@ -3001,6 +3004,9 @@
               "$ref" : "#/items/definitions/org.apache.camel.model.TemplatedRouteParameterDefinition"
             }
           },
+          "prefixId" : {
+            "type" : "string"
+          },
           "routeId" : {
             "type" : "string"
           },
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteTemplateTest.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteTemplateTest.groovy
index 362eb48a614..057c51499b4 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteTemplateTest.groovy
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteTemplateTest.groovy
@@ -484,4 +484,44 @@ class RouteTemplateTest extends YamlTestSupport {
             }
     }
 
+    def "create route-template with prefix"() {
+        setup:
+        loadRoutes """
+                - route-template:
+                    id: "myTemplate"
+                    parameters:
+                      - name: "foo"
+                      - name: "bar"
+                    from:
+                      uri: "direct:{{foo}}"
+                      steps:
+                      - choice:  
+                          when:
+                            - header: "foo"
+                              steps:
+                                - log:
+                                    id: "myLog"
+                                    message: "Hello World"
+                          otherwise:
+                            steps:
+                              - to:
+                                  uri: "mock:{{bar}}"
+                                  id: "end"
+            """
+        when:
+        Map<String, Object> parameters = new HashMap<>();
+        parameters.put("foo", "one");
+        parameters.put("bar", "cheese");
+        context.addRouteFromTemplate("first", "myTemplate", "aaa", parameters);
+
+        parameters.put("foo", "two");
+        parameters.put("bar", "cake");
+        context.addRouteFromTemplate("second", "myTemplate", "bbb", parameters);
+        context.start()
+
+        then:
+        Assertions.assertEquals(3, context.getRoute("first").filter("aaa*").size());
+        Assertions.assertEquals(3, context.getRoute("second").filter("bbb*").size());
+    }
+
 }
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RoutesTest.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RoutesTest.groovy
index b11cabc5afe..32038c0a122 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RoutesTest.groovy
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RoutesTest.groovy
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.yaml
 import org.apache.camel.dsl.yaml.support.YamlTestSupport
 import org.apache.camel.model.LogDefinition
 import org.apache.camel.model.RouteDefinition
+import org.junit.jupiter.api.Assertions
 
 class RoutesTest extends YamlTestSupport {
 
@@ -307,4 +308,36 @@ class RoutesTest extends YamlTestSupport {
         }
     }
 
+    def "load route with node-prefix-id"() {
+        when:
+        loadRoutes '''
+                - route:
+                    id: foo
+                    node-prefix-id: aaa
+                    from:
+                      uri: "direct:foo"
+                      steps:
+                        - to:
+                            id: "myFoo"
+                            uri: "mock:foo"
+                        - to: "seda:foo"
+                - route:
+                    id: bar
+                    node-prefix-id: bbb
+                    from:
+                      uri: "direct:bar"
+                      steps:
+                        - to:
+                            id: "myBar"
+                            uri: "mock:bar"
+                        - to: "seda:bar"
+            '''
+        then:
+        context.routeDefinitions.size() == 2
+        context.start()
+
+        Assertions.assertEquals(2, context.getRoute("foo").filter("aaa*").size());
+        Assertions.assertEquals(2, context.getRoute("bar").filter("bbb*").size());
+    }
+
 }


[camel] 02/04: CAMEL-18798/CAMEL-18771: Allow to configure nodePrefixId on route/routeTemplate to prefix all node IDs to make it easier to avoid clash with hardcoded IDs.

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

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

commit a1d9d5e47925a311a08d401cdbc8c966307812c1
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Dec 7 08:34:45 2022 +0100

    CAMEL-18798/CAMEL-18771: Allow to configure nodePrefixId on route/routeTemplate to prefix all node IDs to make it easier to avoid clash with hardcoded IDs.
---
 .../SpringRouteNodePrefixIdDuplicateTest.java      | 30 ++++++++++++
 .../processor/SpringRouteNodePrefixIdTest.java     | 30 ++++++++++++
 .../SpringTemplatedRoutePrefixIdTest.java          | 54 ++++++++++++++++++++++
 .../SpringRouteNodePrefixIdDuplicateTest.xml       | 40 ++++++++++++++++
 .../processor/SpringRouteNodePrefixIdTest.xml      | 51 ++++++++++++++++++++
 .../SpringTemplatedRoutePrefixIdTest.xml           | 53 +++++++++++++++++++++
 .../camel/builder/RouteTemplatePrefixIdTest.java   | 19 ++++++--
 7 files changed, 273 insertions(+), 4 deletions(-)

diff --git a/components/camel-spring-xml/src/test/java/org/apache/camel/spring/processor/SpringRouteNodePrefixIdDuplicateTest.java b/components/camel-spring-xml/src/test/java/org/apache/camel/spring/processor/SpringRouteNodePrefixIdDuplicateTest.java
new file mode 100644
index 00000000000..9194452c2c9
--- /dev/null
+++ b/components/camel-spring-xml/src/test/java/org/apache/camel/spring/processor/SpringRouteNodePrefixIdDuplicateTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.spring.processor;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.processor.RouteNodePrefixIdDuplicateTest;
+
+import static org.apache.camel.spring.processor.SpringTestHelper.createSpringCamelContext;
+
+public class SpringRouteNodePrefixIdDuplicateTest extends RouteNodePrefixIdDuplicateTest {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        return createSpringCamelContext(this, "org/apache/camel/spring/processor/SpringRouteNodePrefixIdDuplicateTest.xml");
+    }
+}
diff --git a/components/camel-spring-xml/src/test/java/org/apache/camel/spring/processor/SpringRouteNodePrefixIdTest.java b/components/camel-spring-xml/src/test/java/org/apache/camel/spring/processor/SpringRouteNodePrefixIdTest.java
new file mode 100644
index 00000000000..75ee283cfe9
--- /dev/null
+++ b/components/camel-spring-xml/src/test/java/org/apache/camel/spring/processor/SpringRouteNodePrefixIdTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.spring.processor;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.processor.RouteNodePrefixIdTest;
+
+import static org.apache.camel.spring.processor.SpringTestHelper.createSpringCamelContext;
+
+public class SpringRouteNodePrefixIdTest extends RouteNodePrefixIdTest {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        return createSpringCamelContext(this, "org/apache/camel/spring/processor/SpringRouteNodePrefixIdTest.xml");
+    }
+}
diff --git a/components/camel-spring-xml/src/test/java/org/apache/camel/spring/routebuilder/SpringTemplatedRoutePrefixIdTest.java b/components/camel-spring-xml/src/test/java/org/apache/camel/spring/routebuilder/SpringTemplatedRoutePrefixIdTest.java
new file mode 100644
index 00000000000..1b0da5abef3
--- /dev/null
+++ b/components/camel-spring-xml/src/test/java/org/apache/camel/spring/routebuilder/SpringTemplatedRoutePrefixIdTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.spring.routebuilder;
+
+import org.apache.camel.Route;
+import org.apache.camel.spring.SpringTestSupport;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.support.AbstractXmlApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class SpringTemplatedRoutePrefixIdTest extends SpringTestSupport {
+
+    @Override
+    protected AbstractXmlApplicationContext createApplicationContext() {
+        return new ClassPathXmlApplicationContext("org/apache/camel/spring/routebuilder/SpringTemplatedRoutePrefixIdTest.xml");
+    }
+
+    @Test
+    public void testPrefixId() throws Exception {
+        assertEquals(2, context.getRouteDefinitions().size());
+        assertEquals(2, context.getRoutes().size());
+        assertEquals("Started", context.getRouteController().getRouteStatus("first").name());
+        assertEquals("Started", context.getRouteController().getRouteStatus("second").name());
+        assertEquals("true", context.getRoute("first").getProperties().get(Route.TEMPLATE_PROPERTY));
+        assertEquals("true", context.getRoute("second").getProperties().get(Route.TEMPLATE_PROPERTY));
+
+        template.sendBody("direct:one", "Hello Cheese");
+        template.sendBody("direct:two", "Hello Cake");
+
+        assertMockEndpointsSatisfied();
+
+        // all nodes should include prefix
+        Assertions.assertEquals(3, context.getRoute("first").filter("aaa*").size());
+        Assertions.assertEquals(3, context.getRoute("second").filter("bbb*").size());
+    }
+
+}
diff --git a/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/SpringRouteNodePrefixIdDuplicateTest.xml b/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/SpringRouteNodePrefixIdDuplicateTest.xml
new file mode 100644
index 00000000000..14499ff4aee
--- /dev/null
+++ b/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/SpringRouteNodePrefixIdDuplicateTest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
+    ">
+
+  <camelContext xmlns="http://camel.apache.org/schema/spring">
+    <route id="foo" nodePrefixId="aaa">
+      <from uri="direct:foo"/>
+      <to uri="mock:foo" id="myMock"/>
+      <to uri="seda:foo"/>
+    </route>
+    <route id="bar" nodePrefixId="bbb">
+      <from uri="direct:bar"/>
+      <to uri="mock:bar" id="myMock"/>
+      <to uri="seda:bar"/>
+    </route>
+  </camelContext>
+
+</beans>
diff --git a/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/SpringRouteNodePrefixIdTest.xml b/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/SpringRouteNodePrefixIdTest.xml
new file mode 100644
index 00000000000..48ad6f5deaf
--- /dev/null
+++ b/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/SpringRouteNodePrefixIdTest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
+    ">
+
+  <camelContext xmlns="http://camel.apache.org/schema/spring">
+    <route id="foo" nodePrefixId="aaa">
+      <from uri="direct:foo"/>
+      <to uri="mock:foo" id="myFoo"/>
+      <to uri="seda:foo"/>
+    </route>
+    <route id="bar" nodePrefixId="bbb">
+      <from uri="direct:bar"/>
+      <to uri="mock:bar" id="myBar"/>
+      <to uri="seda:bar"/>
+    </route>
+    <route nodePrefixId="ccc">
+      <from uri="direct:cheese"/>
+      <choice>
+        <when><header>cheese</header>
+          <to uri="mock:cheese" id="myCheese"/>
+        </when>
+        <otherwise>
+          <to uri="mock:gauda"/>
+        </otherwise>
+      </choice>
+    </route>
+  </camelContext>
+
+</beans>
diff --git a/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/routebuilder/SpringTemplatedRoutePrefixIdTest.xml b/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/routebuilder/SpringTemplatedRoutePrefixIdTest.xml
new file mode 100644
index 00000000000..76c86bd0c49
--- /dev/null
+++ b/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/routebuilder/SpringTemplatedRoutePrefixIdTest.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+            http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
+            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+    <camelContext id="foo" xmlns="http://camel.apache.org/schema/spring">
+        <routeTemplate id="myTemplate">
+            <templateParameter name="foo"/>
+            <templateParameter name="bar"/>
+            <route>
+                <from uri="direct:{{foo}}"/>
+                <choice>
+                    <when>
+                        <header>foo</header>
+                        <log message="${body}" id="myLog"/>
+                    </when>
+                    <otherwise>
+                        <to uri="mock:{{bar}}" id="end"/>
+                    </otherwise>
+                </choice>
+            </route>
+        </routeTemplate>
+        <templatedRoute routeTemplateRef="myTemplate" routeId="first" prefixId="aaa">
+            <parameter name="foo" value="one"/>
+            <parameter name="bar" value="cheese"/>
+        </templatedRoute>
+        <templatedRoute routeTemplateRef="myTemplate" routeId="second" prefixId="bbb">
+            <parameter name="foo" value="two"/>
+            <parameter name="bar" value="cake"/>
+        </templatedRoute>
+    </camelContext>
+
+</beans>
\ No newline at end of file
diff --git a/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplatePrefixIdTest.java b/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplatePrefixIdTest.java
index eadba72450a..f7fd2012780 100644
--- a/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplatePrefixIdTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplatePrefixIdTest.java
@@ -22,6 +22,7 @@ import java.util.Map;
 import org.apache.camel.ContextTestSupport;
 import org.apache.camel.Route;
 import org.apache.camel.model.RouteTemplateDefinition;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -30,10 +31,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 public class RouteTemplatePrefixIdTest extends ContextTestSupport {
 
-    // TODO: docs
-    // TODO: <xml> tests
-    // TODO: regen
-
     @Test
     public void testDefineRouteTemplate() throws Exception {
         assertEquals(1, context.getRouteTemplateDefinitions().size());
@@ -74,6 +71,10 @@ public class RouteTemplatePrefixIdTest extends ContextTestSupport {
         template.sendBody("direct:two", "Hello Cake");
 
         assertMockEndpointsSatisfied();
+
+        // all nodes should include prefix
+        Assertions.assertEquals(3, context.getRoute("first").filter("aaa*").size());
+        Assertions.assertEquals(3, context.getRoute("second").filter("bbb*").size());
     }
 
     @Test
@@ -112,6 +113,10 @@ public class RouteTemplatePrefixIdTest extends ContextTestSupport {
         template.sendBody("direct:two", "Hello Cake");
 
         assertMockEndpointsSatisfied();
+
+        // all nodes should include prefix
+        Assertions.assertEquals(3, context.getRoute("first").filter("aaa*").size());
+        Assertions.assertEquals(3, context.getRoute("second").filter("bbb*").size());
     }
 
     @Test
@@ -138,6 +143,9 @@ public class RouteTemplatePrefixIdTest extends ContextTestSupport {
         template.sendBody("direct:one", "Hello Cheese");
 
         assertMockEndpointsSatisfied();
+
+        // all nodes should include prefix
+        Assertions.assertEquals(3, context.getRoute(routeId).filter("aaa*").size());
     }
 
     @Test
@@ -174,6 +182,9 @@ public class RouteTemplatePrefixIdTest extends ContextTestSupport {
         template.sendBody("direct:one", "Hello Cheese");
 
         assertMockEndpointsSatisfied();
+
+        // all nodes should include prefix
+        Assertions.assertEquals(3, context.getRoute(routeId).filter("aaa*").size());
     }
 
     @Override