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

davsclaus pushed a commit to branch fix/CAMEL-25616
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 0e6fa130649b34c32df2151ba9a24f4b730d114a
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Oct 13 12:27:42 2026 +0200

    CAMEL-25616: camel-core - route reload no longer duplicates a templated 
route without routeId, nor removes all routes when the route template is 
defined in Java
    
    Two regressions from CAMEL-25554 and CAMEL-25598, reported in the PR 
comments after the merge:
    
    - with routesReloadRemoveAllRoutes=false, a templatedRoute without a 
routeId was created again on every reload
      (a route with a generated id does not replace the existing one). Only 
templated routes with a route id are
      updated now; one without an id is left as it is, as before CAMEL-25554.
    - with routesReloadRemoveAllRoutes=true, when the route template is defined 
in Java and the templatedRoute in a
      watched file, a reload of any file failed (the reload removed the 
template and nothing added it again), and the
      restore failed the same way, leaving the application without routes. The 
file with the templated route is now
      only loaded again when its route template comes from a file, as before 
CAMEL-25598.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../org/apache/camel/builder/RouteBuilder.java     | 10 ++++-
 .../camel/support/RouteWatcherReloadStrategy.java  |  7 +++-
 .../yaml/RouteReloadTemplatedRouteSourceTest.java  | 36 ++++++++++++++++++
 .../dsl/yaml/RouteReloadTemplatedRouteTest.java    | 43 ++++++++++++++++++++--
 4 files changed, 90 insertions(+), 6 deletions(-)

diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java
index 35d2ce7e1db0..1c9f214c5376 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java
@@ -828,7 +828,12 @@ public abstract class RouteBuilder extends BuilderSupport 
implements RoutesBuild
     @Override
     public Set<String> updateTemplatedRoutesToCamelContext(CamelContext 
context) throws Exception {
         Set<String> answer = new LinkedHashSet<>();
-        if (getTemplatedRouteCollection().getTemplatedRoutes().isEmpty()) {
+        // only a templated route with a route id replaces its existing route: 
one without an id would be created
+        // again next to the existing route on every update, so it is left as 
it is (CAMEL-25616)
+        List<TemplatedRouteDefinition> templatedRoutes = 
getTemplatedRouteCollection().getTemplatedRoutes().stream()
+                .filter(t -> t.getRouteId() != null)
+                .toList();
+        if (templatedRoutes.isEmpty()) {
             return answer;
         }
 
@@ -838,7 +843,8 @@ public abstract class RouteBuilder extends BuilderSupport 
implements RoutesBuild
         Set<RouteDefinition> before = Collections.newSetFromMap(new 
IdentityHashMap<>());
         before.addAll(model.getRouteDefinitions());
 
-        populateTemplatedRoutes(context);
+        getTemplatedRouteCollection().setCamelContext(context);
+        model.addRouteFromTemplatedRoutes(templatedRoutes);
 
         for (RouteDefinition route : model.getRouteDefinitions()) {
             if (!before.contains(route) && route.getRouteId() != null) {
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java
index 7aad6a8ad530..22be1ad2f8fc 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java
@@ -378,10 +378,15 @@ public class RouteWatcherReloadStrategy extends 
FileWatcherResourceReloadStrateg
                 // that exist, so its routes are removed like those of a 
changed file, CAMEL-25427)
                 // (a route from a templated route needs the file with the 
route template, and the file with the
                 // templated route, CAMEL-25598; a route from a Kamelet is 
created again by its parent route)
+                // (the file with the templated route is only loaded again 
when its route template comes from a file:
+                // a route template added from Java is removed below and 
nothing adds it again, so loading the
+                // templated route would fail the whole reload, CAMEL-25616)
                 getCamelContext().getRoutes().forEach(r -> {
                     if (!r.isCreatedByKamelet()) {
                         addRouteSource(resources, sources, 
r.getSourceResource());
-                        addRouteSource(resources, sources, 
r.getTemplatedRouteResource());
+                        if (r.getSourceResource() != null) {
+                            addRouteSource(resources, sources, 
r.getTemplatedRouteResource());
+                        }
                     }
                 });
                 // first stop and remove all routes
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/RouteReloadTemplatedRouteSourceTest.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/RouteReloadTemplatedRouteSourceTest.java
index d6b3d0fe6af4..114d2cf48074 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/RouteReloadTemplatedRouteSourceTest.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/RouteReloadTemplatedRouteSourceTest.java
@@ -25,6 +25,7 @@ import java.nio.file.attribute.BasicFileAttributes;
 
 import org.apache.camel.Route;
 import org.apache.camel.ServiceStatus;
+import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.dsl.yaml.support.YamlTestSupport;
 import org.apache.camel.support.ResourceHelper;
 import org.apache.camel.support.RouteWatcherReloadStrategy;
@@ -137,6 +138,41 @@ class RouteReloadTemplatedRouteSourceTest extends 
YamlTestSupport {
         }
     }
 
+    @Test
+    void reloadOfAnotherFileWithTheRouteTemplateFromJava() throws Exception {
+        // the route template comes from Java (no file) and the templated 
route from a file: a reload of another file
+        // must not fail on the route template that the reload removed 
(CAMEL-25616)
+        context.getRouteController().removeAllRoutes();
+        context.removeRouteTemplates("*");
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                routeTemplate("greeting").templateParameter("greeting")
+                        
.from("direct:greet").setBody(constant("{{greeting}}"));
+            }
+        });
+        loadRoutes(ResourceHelper.resolveResource(context, "file:" + uses),
+                ResourceHelper.resolveResource(context, "file:" + plain));
+        assertThat(context.getRoute("greet").getSourceResource()).isNull();
+
+        TestableStrategy strategy = new TestableStrategy(dir.toString());
+        strategy.setCamelContext(context);
+        strategy.setPattern("*.yaml");
+        strategy.doStart();
+        try {
+            Files.writeString(plain, plainRoute("plain changed"));
+            strategy.getResourceReload().onReload(plain.toString(),
+                    ResourceHelper.resolveResource(context, "file:" + plain));
+
+            assertThat(strategy.getFailedCounter()).isZero();
+            assertThat(strategy.getLastError()).isNull();
+            
assertThat(context.createFluentProducerTemplate().to("direct:plain").request(String.class))
+                    .isEqualTo("plain changed");
+        } finally {
+            strategy.doStop();
+        }
+    }
+
     private static String plainRoute(String body) {
         return """
                 - route:
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
index 2913ba7a8d39..1d30755bfa30 100644
--- 
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
@@ -99,7 +99,45 @@ class RouteReloadTemplatedRouteTest extends YamlTestSupport {
         }
     }
 
+    @Test
+    void reloadKeepingTheOtherRoutesTemplatedRouteWithoutId() throws Exception 
{
+        // a templated route without a route id cannot replace its existing 
route, so it is left as it is (CAMEL-25616)
+        Files.writeString(file, routesWithoutTemplatedRouteId("Hello"));
+        context.getRouteController().removeAllRoutes();
+        context.removeRouteTemplates("*");
+        loadRoutes(ResourceHelper.resolveResource(context, "file:" + file));
+        assertThat(context.getRoutes()).hasSize(2);
+
+        TestableStrategy strategy = new TestableStrategy(dir.toString());
+        strategy.setCamelContext(context);
+        strategy.setPattern("*.yaml");
+        strategy.setRemoveAllRoutes(false);
+        strategy.doStart();
+        try {
+            for (int i = 0; i < 3; i++) {
+                Files.writeString(file, routesWithoutTemplatedRouteId("Hi" + 
i));
+                strategy.getResourceReload().onReload(file.toString(),
+                        ResourceHelper.resolveResource(context, "file:" + 
file));
+
+                assertThat(strategy.getFailedCounter()).isZero();
+                assertThat(context.getRoutes()).hasSize(2);
+                
assertThat(context.createFluentProducerTemplate().to("direct:plain").request(String.class))
+                        .isEqualTo("plain Hi" + i);
+            }
+        } finally {
+            strategy.doStop();
+        }
+    }
+
     private static String routes(String greeting) {
+        return routes(greeting, true);
+    }
+
+    private static String routesWithoutTemplatedRouteId(String greeting) {
+        return routes(greeting, false);
+    }
+
+    private static String routes(String greeting, boolean templatedRouteId) {
         return """
                 - routeTemplate:
                     id: "greeting"
@@ -112,8 +150,7 @@ class RouteReloadTemplatedRouteTest extends YamlTestSupport 
{
                             constant: "{{greeting}}"
                 - templatedRoute:
                     routeTemplateRef: "greeting"
-                    routeId: "greet"
-                    parameters:
+                %s    parameters:
                       - name: "greeting"
                         value: "%s"
                 - route:
@@ -123,7 +160,7 @@ class RouteReloadTemplatedRouteTest extends YamlTestSupport 
{
                       steps:
                         - setBody:
                             constant: "plain %s"
-                """.formatted(greeting, greeting);
+                """.formatted(templatedRouteId ? "    routeId: \"greet\"\n" : 
"", greeting, greeting);
     }
 
     private static void deleteRecursively(Path path) throws IOException {

Reply via email to