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 66ee0906db1d CAMEL-25068: camel-core - Route templates: fail when the 
route id of a route from a template is already used (item 2) (#27467)
66ee0906db1d is described below

commit 66ee0906db1d73a22d3b437e1be01ffe747c0d31
Author: allthingssecurity <[email protected]>
AuthorDate: Wed Oct 7 16:48:45 2026 +0530

    CAMEL-25068: camel-core - Route templates: fail when the route id of a 
route from a template is already used (item 2) (#27467)
    
    A route created from a route template (or a Kamelet) with the id of an 
existing
    route that was not created from the same template now fails with
    FailedToCreateRouteFromTemplateException instead of silently replacing it.
    A route from the same template is still replaced, so updating the parent 
route
    of a Kamelet endpoint keeps working. Also fixes a NullPointerException in 
the
    duplicate node id error of a route from a template without a route id.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../kamelet/KameletExistingRouteIdTest.java        | 164 +++++++++++++++++++++
 .../java/org/apache/camel/impl/DefaultModel.java   |  19 ++-
 .../org/apache/camel/model/RouteDefinition.java    |  14 ++
 .../builder/RouteTemplateExistingRouteIdTest.java  | 146 ++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |  12 ++
 .../dsl/yaml/RouteReloadTemplatedRouteTest.java    | 144 ++++++++++++++++++
 6 files changed, 498 insertions(+), 1 deletion(-)

diff --git 
a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletExistingRouteIdTest.java
 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletExistingRouteIdTest.java
new file mode 100644
index 000000000000..9cebb3e285bc
--- /dev/null
+++ 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletExistingRouteIdTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.FailedToCreateRouteFromTemplateException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.apache.camel.util.ObjectHelper;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * The route of a Kamelet endpoint with a route id must not replace an 
existing route that was not created from the same
+ * Kamelet, while the route of the same Kamelet is created again when its 
parent route is updated.
+ */
+class KameletExistingRouteIdTest extends CamelTestSupport {
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+
+    @Test
+    void routeIdOfRegularRoute() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                routeTemplate("echo")
+                        .templateParameter("prefix")
+                        .from("kamelet:source")
+                        .setBody().simple("{{prefix}}-${body}");
+
+                from("direct:existing").routeId("existing")
+                        .setBody().constant("existing");
+            }
+        });
+        context.start();
+
+        assertThatThrownBy(() -> context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                
from("direct:b").routeId("b").to("kamelet:echo/existing?prefix=b");
+            }
+        
})).hasRootCauseInstanceOf(FailedToCreateRouteFromTemplateException.class)
+                .satisfies(e -> 
assertThat(ObjectHelper.getException(FailedToCreateKameletException.class, 
e)).isNotNull());
+
+        // the regular route is kept
+        
assertThat(context.getRoute("existing").getEndpoint().getEndpointUri()).isEqualTo("direct://existing");
+        assertThat(template.requestBody("direct:existing", 
"x")).isEqualTo("existing");
+    }
+
+    @Test
+    void routeIdOfRouteFromOtherKamelet() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                routeTemplate("echo")
+                        .templateParameter("prefix")
+                        .from("kamelet:source")
+                        .setBody().simple("{{prefix}}-${body}");
+
+                routeTemplate("shout")
+                        .templateParameter("prefix")
+                        .from("kamelet:source")
+                        .setBody().simple("{{prefix}}-${body.toUpperCase()}");
+            }
+        });
+        context.start();
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:a").routeId("a").to("kamelet:echo/myId?prefix=a");
+            }
+        });
+        assertThat(template.requestBody("direct:a", "x")).isEqualTo("a-x");
+
+        assertThatThrownBy(() -> context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                
from("direct:b").routeId("b").to("kamelet:shout/myId?prefix=b");
+            }
+        
})).hasRootCauseInstanceOf(FailedToCreateRouteFromTemplateException.class)
+                .satisfies(e -> 
assertThat(ObjectHelper.getException(FailedToCreateKameletException.class, 
e)).isNotNull());
+
+        // the route of the first Kamelet endpoint is kept
+        assertThat(template.requestBody("direct:a", "x")).isEqualTo("a-x");
+    }
+
+    @Test
+    void updateParent() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                routeTemplate("echo")
+                        .templateParameter("prefix")
+                        .from("kamelet:source")
+                        .setBody().simple("{{prefix}}-${body}");
+
+                from("direct:a").routeId("a").to("kamelet:echo/myId?prefix=a");
+            }
+        });
+        context.start();
+        assertThat(template.requestBody("direct:a", "x")).isEqualTo("a-x");
+
+        // as a route reload with removeAllRoutes=false does
+        new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:a").routeId("a").to("kamelet:echo/myId?prefix=b");
+            }
+        }.updateRoutesToCamelContext(context);
+        assertThat(template.requestBody("direct:a", "x")).isEqualTo("b-x");
+    }
+
+    @Test
+    void removeAndReAddParent() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                routeTemplate("echo")
+                        .templateParameter("prefix")
+                        .from("kamelet:source")
+                        .setBody().simple("{{prefix}}-${body}");
+            }
+        });
+        context.start();
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:a").routeId("a").to("kamelet:echo/myId?prefix=a");
+            }
+        });
+        assertThat(template.requestBody("direct:a", "x")).isEqualTo("a-x");
+
+        context.getRouteController().stopRoute("a");
+        context.removeRoute("a");
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:a").routeId("a").to("kamelet:echo/myId?prefix=b");
+            }
+        });
+        assertThat(template.requestBody("direct:a", "x")).isEqualTo("b-x");
+    }
+}
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 0d740ce89a6c..c14a3d0dbde8 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
@@ -648,6 +648,22 @@ public class DefaultModel implements Model {
         if (group != null) {
             def.setGroup(group);
         }
+        // a route from a template (or a Kamelet) must not replace an existing 
route with the same id, unless that
+        // route was created from the same template (such as the route of a 
Kamelet with a route id that is created
+        // again when its parent route is updated); a route that exists in the 
CamelContext but has no route
+        // definition in the model is never replaced
+        String id = def.getId();
+        if (id != null) {
+            RouteDefinition existing = getRouteDefinition(id);
+            boolean sameTemplate = existing != null && 
routeTemplateId.equals(existing.getRouteTemplateId());
+            if (!sameTemplate && (existing != null || 
camelContext.getRoute(id) != null)) {
+                throw new FailedToCreateRouteFromTemplateException(
+                        id, routeTemplateId,
+                        "Route with id: " + id
+                                             + " already exists. Remove the 
existing route first or use another route id.");
+            }
+        }
+        def.setRouteTemplateId(routeTemplateId);
         def.setTemplateParameters(prop);
         def.setTemplateDefaultParameters(propDefaultValues);
         def.setRouteTemplateContext(routeTemplateContext);
@@ -674,7 +690,8 @@ public class DefaultModel implements Model {
         String duplicate = RouteDefinitionHelper.validateUniqueIds(def, 
routeDefinitions, prefixId);
         if (duplicate != null) {
             throw new FailedToCreateRouteFromTemplateException(
-                    routeId, routeTemplateId,
+                    
def.idOrCreate(camelContext.getCamelContextExtension().getContextPlugin(NodeIdFactory.class)),
+                    routeTemplateId,
                     "Duplicate id detected: " + duplicate + ". Please correct 
ids to be unique among all your routes.");
         }
 
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 3e94918b6868..d28f710977b6 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
@@ -97,6 +97,7 @@ public class RouteDefinition extends 
OutputDefinition<RouteDefinition>
     private Map<String, Object> templateParameters;
     private Map<String, Object> templateDefaultParameters;
     private RouteTemplateContext routeTemplateContext;
+    private String routeTemplateId;
     private Resource resource;
     private String precondition;
 
@@ -893,6 +894,19 @@ public class RouteDefinition extends 
OutputDefinition<RouteDefinition>
         this.routeTemplateContext = routeTemplateContext;
     }
 
+    /**
+     * The id of the route template (or Kamelet) this route was created from, 
or <tt>null</tt> if the route was not
+     * created from a route template.
+     */
+    public String getRouteTemplateId() {
+        return routeTemplateId;
+    }
+
+    @XmlTransient
+    public void setRouteTemplateId(String routeTemplateId) {
+        this.routeTemplateId = routeTemplateId;
+    }
+
     public Resource getResource() {
         return resource;
     }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateExistingRouteIdTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateExistingRouteIdTest.java
new file mode 100644
index 000000000000..abef275a61ba
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/builder/RouteTemplateExistingRouteIdTest.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.builder;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.FailedToCreateRouteFromTemplateException;
+import org.apache.camel.model.ModelCamelContext;
+import org.apache.camel.model.TemplatedRouteDefinition;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * A route created from a template must not replace an existing route with the 
same id, unless that route was created
+ * from the same template.
+ */
+class RouteTemplateExistingRouteIdTest extends ContextTestSupport {
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+
+    @BeforeEach
+    void addRoutes() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                routeTemplate("myTemplate")
+                        .templateParameter("foo")
+                        .from("direct:{{foo}}")
+                        .setBody(simple("template {{foo}}"));
+
+                routeTemplate("otherTemplate")
+                        .templateParameter("foo")
+                        .from("direct:other-{{foo}}")
+                        .setBody(simple("other {{foo}}"));
+
+                from("direct:existing").routeId("existing")
+                        .setBody(constant("existing"));
+            }
+        });
+        context.start();
+    }
+
+    @Test
+    void builderRouteIdOfExistingRoute() {
+        assertThatThrownBy(() -> TemplatedRouteBuilder.builder(context, 
"myTemplate")
+                .routeId("existing")
+                .parameter("foo", "one")
+                .add())
+                .isInstanceOf(FailedToCreateRouteFromTemplateException.class)
+                .hasMessageContaining("existing");
+
+        assertExistingRouteUnchanged();
+        assertThat(context.hasEndpoint("direct:one")).isNull();
+    }
+
+    @Test
+    void templatedRouteIdOfExistingRoute() {
+        TemplatedRouteDefinition def = new TemplatedRouteDefinition();
+        def.setRouteTemplateRef("myTemplate");
+        def.setRouteId("existing");
+        def.parameter("foo", "one");
+
+        assertThatThrownBy(() -> ((ModelCamelContext) 
context).addRouteFromTemplatedRoute(def))
+                .isInstanceOf(FailedToCreateRouteFromTemplateException.class)
+                .hasMessageContaining("existing");
+
+        assertExistingRouteUnchanged();
+    }
+
+    @Test
+    void routeIdOfRouteFromSameTemplate() {
+        TemplatedRouteBuilder.builder(context, 
"myTemplate").routeId("mine").parameter("foo", "one").add();
+
+        // a route from the same template is replaced (as before), which is 
how a route from a template is updated
+        TemplatedRouteBuilder.builder(context, 
"myTemplate").routeId("mine").parameter("foo", "two").add();
+
+        assertThat(context.getRoutes()).hasSize(2);
+        
assertThat(context.getRoute("mine").getEndpoint().getEndpointUri()).isEqualTo("direct://two");
+        assertThat(template.requestBody("direct:two", 
"x")).isEqualTo("template two");
+    }
+
+    @Test
+    void routeIdOfRouteFromOtherTemplate() {
+        TemplatedRouteBuilder.builder(context, 
"myTemplate").routeId("mine").parameter("foo", "one").add();
+
+        assertThatThrownBy(() -> TemplatedRouteBuilder.builder(context, 
"otherTemplate")
+                .routeId("mine")
+                .parameter("foo", "two")
+                .add())
+                .isInstanceOf(FailedToCreateRouteFromTemplateException.class)
+                .hasMessageContaining("mine");
+
+        assertThat(context.getRoutes()).hasSize(2);
+        
assertThat(context.getRoute("mine").getEndpoint().getEndpointUri()).isEqualTo("direct://one");
+        assertThat(template.requestBody("direct:one", 
"x")).isEqualTo("template one");
+        assertThat(context.hasEndpoint("direct:other-two")).isNull();
+    }
+
+    @Test
+    void duplicateNodeIdWithoutRouteId() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                routeTemplate("fixedNodeId")
+                        .templateParameter("foo")
+                        .from("direct:{{foo}}")
+                        .setBody(constant("fixed")).id("fixed");
+            }
+        });
+        TemplatedRouteBuilder.builder(context, 
"fixedNodeId").prefixId("p").parameter("foo", "one").add();
+
+        // the same prefix gives the same node id, which is reported as such 
(also without a route id)
+        assertThatThrownBy(() -> TemplatedRouteBuilder.builder(context, 
"fixedNodeId")
+                .prefixId("p")
+                .parameter("foo", "two")
+                .add())
+                .isInstanceOf(FailedToCreateRouteFromTemplateException.class)
+                .hasMessageContaining("Duplicate id detected");
+    }
+
+    private void assertExistingRouteUnchanged() {
+        assertThat(context.getRoutes()).hasSize(1);
+        assertThat(context.getRouteDefinitions()).hasSize(1);
+        
assertThat(context.getRoute("existing").getEndpoint().getEndpointUri()).isEqualTo("direct://existing");
+        assertThat(template.requestBody("direct:existing", 
"x")).isEqualTo("existing");
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 3164ab7f11d3..380ef42e8968 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -507,6 +507,18 @@ The 
`org.apache.camel.processor.InterceptSendToEndpointCallback` class is deprec
   the first route are now kept as beans of the caller, so the later routes 
share these instances instead of each
   getting new ones. Use a new `RouteTemplateContext` per route to get new 
template beans for each route.
 
+==== A route id that is already used (Breaking change)
+
+Creating a route from a route template (`TemplatedRouteBuilder`, 
`templatedRoute`, `addRouteFromTemplate`, or a
+Kamelet endpoint with a route id) with the id of an existing route that was 
not created from the same template (a
+regular route, or a route from another template or Kamelet) now fails with 
`FailedToCreateRouteFromTemplateException`.
+For a Kamelet endpoint this exception is wrapped in a 
`FailedToCreateKameletException`. Prior to Camel 4.23 the
+existing route was removed and replaced without notice. To replace such a 
route, stop and remove it first.
+
+An existing route that was created from the same template is still replaced, 
as before. This is how the route of a
+Kamelet endpoint with a route id is created again when its parent route is 
updated (for example by a route reload),
+and how a route from a template is updated with other parameters.
+
 === camel-aws2-ddb
 
 The `aws2-ddb:application-json` data type transformer no longer overwrites a 
`CamelAwsDdbReturnValues` header that
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/RouteReloadTemplatedRouteTest.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/RouteReloadTemplatedRouteTest.java
new file mode 100644
index 000000000000..a2aa2993be69
--- /dev/null
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/RouteReloadTemplatedRouteTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.dsl.yaml;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+import org.apache.camel.ServiceStatus;
+import org.apache.camel.dsl.yaml.support.YamlTestSupport;
+import org.apache.camel.support.ResourceHelper;
+import org.apache.camel.support.RouteWatcherReloadStrategy;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * CAMEL-25068: a route from a template fails when a route with its id exists. 
Reloading a file with a templated route
+ * (keeping the other routes) must still work.
+ */
+class RouteReloadTemplatedRouteTest extends YamlTestSupport {
+
+    /** Subclass to expose protected lifecycle methods for testing without a 
live file watcher. */
+    static class TestableStrategy extends RouteWatcherReloadStrategy {
+        TestableStrategy(String dir) {
+            super(dir);
+        }
+
+        @Override
+        public void doStart() throws Exception {
+            super.doStart();
+        }
+
+        @Override
+        public void doStop() throws Exception {
+            super.doStop();
+        }
+    }
+
+    private Path dir;
+    private Path file;
+
+    @Override
+    public void doSetup() throws Exception {
+        dir = Files.createTempDirectory("camel-reload");
+        file = dir.resolve("greet.camel.yaml");
+        Files.writeString(file, routes("Hello"));
+        context.start();
+        loadRoutes(ResourceHelper.resolveResource(context, "file:" + file));
+    }
+
+    @Override
+    public void doCleanup() throws Exception {
+        deleteRecursively(dir);
+    }
+
+    @Test
+    void reloadKeepingTheOtherRoutes() throws Exception {
+        TestableStrategy strategy = new TestableStrategy(dir.toString());
+        strategy.setCamelContext(context);
+        strategy.setPattern("*.yaml");
+        strategy.setRemoveAllRoutes(false);
+        // the strategy is not started (no file watcher in the test): its 
reload callback is driven by hand
+        strategy.doStart();
+        try {
+            
assertThat(context.getRouteController().getRouteStatus("greet")).isEqualTo(ServiceStatus.Started);
+
+            Files.writeString(file, routes("Hi"));
+            strategy.getResourceReload().onReload(file.toString(),
+                    ResourceHelper.resolveResource(context, "file:" + file));
+
+            // the reload does not fail on the id of the templated route, and 
both routes run
+            
assertThat(context.getRouteController().getRouteStatus("greet")).isEqualTo(ServiceStatus.Started);
+            
assertThat(context.getRouteController().getRouteStatus("plain")).isEqualTo(ServiceStatus.Started);
+            
assertThat(context.createFluentProducerTemplate().to("direct:plain").request(String.class))
+                    .isEqualTo("plain Hi");
+        } finally {
+            strategy.doStop();
+        }
+    }
+
+    private static String routes(String greeting) {
+        return """
+                - routeTemplate:
+                    id: "greeting"
+                    parameters:
+                      - name: "greeting"
+                    from:
+                      uri: "direct:greet"
+                      steps:
+                        - setBody:
+                            constant: "{{greeting}}"
+                - templatedRoute:
+                    routeTemplateRef: "greeting"
+                    routeId: "greet"
+                    parameters:
+                      - name: "greeting"
+                        value: "%s"
+                - route:
+                    id: plain
+                    from:
+                      uri: direct:plain
+                      steps:
+                        - setBody:
+                            constant: "plain %s"
+                """.formatted(greeting, greeting);
+    }
+
+    private static void deleteRecursively(Path path) throws IOException {
+        if (path == null || !Files.exists(path)) {
+            return;
+        }
+        Files.walkFileTree(path, new SimpleFileVisitor<>() {
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes 
attrs) throws IOException {
+                Files.delete(file);
+                return FileVisitResult.CONTINUE;
+            }
+
+            @Override
+            public FileVisitResult postVisitDirectory(Path dir, IOException 
exc) throws IOException {
+                Files.delete(dir);
+                return FileVisitResult.CONTINUE;
+            }
+        });
+    }
+}

Reply via email to