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 {
