This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-25619-reload-route-templates in repository https://gitbox.apache.org/repos/asf/camel.git
commit 65e5ab73390307f4dd18bf1737dc73e4bfeb18b5 Author: Claus Ibsen <[email protected]> AuthorDate: Thu Nov 12 22:19:05 2026 +0100 CAMEL-25619: camel-core - route reload keeps route templates defined in Java, and loads a route template file used through kamelet: again Dev mode route reload (RouteWatcherReloadStrategy) removed all route templates with removeRouteTemplates("*") and loaded the route files again: - A route template added from Java (a RouteBuilder added to the context, no resource) was removed and nothing added it back, so since CAMEL-25616 the file with its templatedRoute was not loaded again either, and the templated route was missing after a reload of another file. - A route template in a watched file that is only used through kamelet: was not loaded again (a route from a Kamelet has no source of its own), so the route calling kamelet:<name> failed on a reload of another file, and the restore of the previous routes failed the same way. Now: - A new CamelContext.removeRouteTemplatesFromResources(pattern) removes only the route templates that were loaded from a resource. The reload and the restore of a failed reload use it, so a route template from Java is kept, and the file with the templated route is loaded again. - The file with the route template of a route created by a Kamelet is loaded again when it is a file (a Kamelet from the classpath is loaded again by the kamelet component when it is used). - The files with route templates are loaded before the other files, in the reload and in the restore, so the route template is there when a route that uses it through kamelet: is created, also when it is the changed file. Upgrade guide: the new CamelContext method. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../main/java/org/apache/camel/CamelContext.java | 12 ++ .../camel/impl/engine/SimpleCamelContext.java | 5 + .../org/apache/camel/impl/DefaultCamelContext.java | 14 ++ .../camel/support/RouteWatcherReloadStrategy.java | 87 +++++++++-- .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 8 + ...ava => RouteReloadKameletTemplateFileTest.java} | 166 ++++++++++----------- .../yaml/RouteReloadTemplatedRouteSourceTest.java | 74 +++++++-- 7 files changed, 253 insertions(+), 113 deletions(-) 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 c6ca6d78cd11..3a929e06399c 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 @@ -849,6 +849,18 @@ public interface CamelContext extends CamelContextLifecycle, RuntimeConfiguratio */ void removeRouteTemplates(String pattern) throws Exception; + /** + * Removes the route templates matching the pattern that were loaded from a resource, such as a route file or a + * Kamelet, and keeps those without a resource, such as the route templates of a Java {@code RouteBuilder} added to + * the context. A route reload loads the resources again, so it removes their route templates with this: nothing + * would add a route template without a resource back. + * + * @param pattern pattern, such as * for all, or foo* to remove all foo templates + * @throws Exception is thrown if error during removing route templates + * @since 4.24 + */ + void removeRouteTemplatesFromResources(String pattern) throws Exception; + /** * Adds the given route policy factory * 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 5eb8397859da..3c291d3ef3da 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 @@ -792,6 +792,11 @@ public class SimpleCamelContext extends AbstractCamelContext { throw new UnsupportedOperationException(); } + @Override + public void removeRouteTemplatesFromResources(String pattern) throws Exception { + throw new UnsupportedOperationException(); + } + @Override public List<NamedRoute> getNamedRouteDefinitions() { 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 6117f47a43f5..388310c8f09b 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 @@ -77,6 +77,7 @@ import org.apache.camel.spi.ValidatorKey; import org.apache.camel.support.CamelContextHelper; import org.apache.camel.support.DefaultRegistry; import org.apache.camel.support.LocalBeanRegistry; +import org.apache.camel.support.PatternHelper; import org.apache.camel.support.ResolverHelper; import org.apache.camel.support.SimpleUuidGenerator; import org.apache.camel.support.scan.AssignableToPackageScanFilter; @@ -436,6 +437,19 @@ public class DefaultCamelContext extends SimpleCamelContext implements ModelCame } } + @Override + public void removeRouteTemplatesFromResources(String pattern) throws Exception { + if (!isLockModel()) { + List<RouteTemplateDefinition> remove = new ArrayList<>(); + for (RouteTemplateDefinition def : new ArrayList<>(model.getRouteTemplateDefinitions())) { + if (def.getResource() != null && PatternHelper.matchPattern(def.getId(), pattern)) { + remove.add(def); + } + } + model.removeRouteTemplateDefinitions(remove); + } + } + @Override public List<NamedRoute> getNamedRouteDefinitions() { return new ArrayList<>(model.getRouteDefinitions()); 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 460c99da95e9..397b58124d2e 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 @@ -78,6 +78,11 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg private String pattern; private boolean removeAllRoutes = true; private final List<Resource> previousSources = new ArrayList<>(); + /** + * The sources with the route templates that the routes of the last reload use, also through {@code kamelet:}: they + * are loaded before the other sources, so a route template is there when a route that uses it is created. + */ + private final List<Resource> templateSources = new ArrayList<>(); /** * The content of the sources as they last loaded, at startup and then at each successful reload, by location: what * a failed reload goes back to. @@ -607,15 +612,19 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg // (the file of a route may have been removed since it loaded: on-demand reload only passes the files // 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 -> addRouteSources(resources, sources, r)); + // templated route, CAMEL-25598; a route from a Kamelet is created again by its parent route, and the + // file with its route template is loaded again when it is a file, CAMEL-25619) + templateSources.clear(); + getCamelContext().getRoutes().forEach(r -> { + addRouteSources(resources, sources, r); + addTemplateSource(templateSources, r); + }); // first stop and remove all routes getCamelContext().getRouteController().removeAllRoutes(); - // remove left-over route templates and endpoints, so we can start on a fresh - getCamelContext().removeRouteTemplates("*"); + // remove left-over route templates and endpoints, so we can start on a fresh: only the route templates + // that were loaded from a resource, as the reload loads those again; a route template added from Java + // has none, and nothing would add it back (CAMEL-25619) + getCamelContext().removeRouteTemplatesFromResources("*"); getCamelContext().getEndpointRegistry().clear(); } @@ -650,6 +659,10 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg } } + // the sources with route templates first, so a route template is there when a route that uses it is + // created (a route template used through kamelet:, CAMEL-25619) + templateSourcesFirst(sources); + // just in case remember this set of sources as what was attempted previously to update // in case the update fails with an exception previousSources.clear(); @@ -861,10 +874,13 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg } } } + // the failed file goes back from memory at the end: it may have the route templates the others use + templateSourcesFirst(restore); try { - // a partial load may have left routes or endpoints behind + // a partial load may have left routes or endpoints behind (the route templates added from Java are kept, + // as in the reload, CAMEL-25619) getCamelContext().getRouteController().removeAllRoutes(); - getCamelContext().removeRouteTemplates("*"); + getCamelContext().removeRouteTemplatesFromResources("*"); getCamelContext().getEndpointRegistry().clear(); if (restore.isEmpty()) { LOG.warn("Reload failed and there are no previous routes to restore: the application runs without" @@ -887,16 +903,59 @@ public class RouteWatcherReloadStrategy extends FileWatcherResourceReloadStrateg /** * Adds the sources of a route to the sources to load again: the file it was loaded from and, for a route from a - * templated route, the file of the templated route. A route from a Kamelet has none of its own: its parent route - * creates it again. + * templated route, the file of the templated route. The route template of a templated route is either in a file, + * which is loaded again, or added without a resource (from Java), which a reload keeps (CAMEL-25619). + * <p/> + * A route from a Kamelet is created again by its parent route. Its source is the file with its route template: when + * that is a file, it is loaded again too, as a route template that is only used through {@code kamelet:} has no + * route of its own that would bring it back (CAMEL-25619). A Kamelet from the classpath, such as from a Kamelet + * catalog, is loaded again by the kamelet component when it is used. */ private static void addRouteSources(Collection<Resource> resources, List<Resource> sources, Route route) { - if (!route.isCreatedByKamelet()) { + if (route.isCreatedByKamelet()) { + if (isFile(route.getSourceResource())) { + addRouteSource(resources, sources, route.getSourceResource()); + } + } else { addRouteSource(resources, sources, route.getSourceResource()); - if (route.getSourceResource() != null) { - addRouteSource(resources, sources, route.getTemplatedRouteResource()); + addRouteSource(resources, sources, route.getTemplatedRouteResource()); + } + } + + /** + * Adds the source of a route created from a route template (a templated route, or a Kamelet) to the sources with + * route templates: the file with its route template. + */ + private static void addTemplateSource(List<Resource> templateSources, Route route) { + Resource rs = route.getSourceResource(); + if (route.getRouteTemplateId() != null && rs != null && !equalResourceLocation(templateSources, rs)) { + templateSources.add(rs); + } + } + + /** + * Moves the sources with route templates before the other sources, keeping the order of each. + */ + private void templateSourcesFirst(List<Resource> sources) { + if (templateSources.isEmpty() || sources.size() < 2) { + return; + } + List<Resource> first = new ArrayList<>(); + List<Resource> rest = new ArrayList<>(); + for (Resource rs : sources) { + if (equalResourceLocation(templateSources, rs)) { + first.add(rs); + } else { + rest.add(rs); } } + sources.clear(); + sources.addAll(first); + sources.addAll(rest); + } + + private static boolean isFile(Resource resource) { + return resource != null && resource.getLocation() != null && resource.getLocation().startsWith("file:"); } /** diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc index 2b76f4e30d41..d9baa7d2cf37 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc @@ -323,6 +323,14 @@ returns the lock held while routes are added to or removed from the model. A cus implement it (classes that extend `DefaultCamelContext` need no change). The Kamelet component now creates the routes of Kamelets holding this lock. +=== camel-core - CamelContext has a new removeRouteTemplatesFromResources method + +The `CamelContext` interface has a new method `removeRouteTemplatesFromResources(String pattern)`, which removes the +route templates loaded from a resource (such as a route file or a Kamelet) and keeps those without one (such as the route +templates of a Java `RouteBuilder` added to the context). A custom implementation of `CamelContext` must implement it +(classes that extend `DefaultCamelContext` need no change). The route reload of dev mode uses it, so a route template +added from Java is kept across a reload, and a route created from it with a templated route in a file is created again. + === camel-core - Route.clearRouteModel() is deprecated The `Route.clearRouteModel()` method is deprecated and is now a no-op. It will be removed in a later release. 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/RouteReloadKameletTemplateFileTest.java similarity index 50% copy from dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/RouteReloadTemplatedRouteSourceTest.java copy to dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/RouteReloadKameletTemplateFileTest.java index 114d2cf48074..ffaacdd8fdd9 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/RouteReloadKameletTemplateFileTest.java @@ -23,9 +23,7 @@ import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; 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; @@ -34,10 +32,10 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; /** - * CAMEL-25598: a route created from a templated route records the file with its route template (as its source) and the - * file with the templated route, so a reload of another file loads both again. + * CAMEL-25619: a route template in a watched file that a route uses only through {@code kamelet:} (no templated route) + * is loaded again on a reload, before the route that uses it. */ -class RouteReloadTemplatedRouteSourceTest extends YamlTestSupport { +class RouteReloadKameletTemplateFileTest extends YamlTestSupport { /** Subclass to expose protected lifecycle methods for testing without a live file watcher. */ static class TestableStrategy extends RouteWatcherReloadStrategy { @@ -60,117 +58,113 @@ class RouteReloadTemplatedRouteSourceTest extends YamlTestSupport { private Path templates; private Path uses; private Path plain; + private TestableStrategy strategy; @Override public void doSetup() throws Exception { - dir = Files.createTempDirectory("camel-reload-templated-source"); + dir = Files.createTempDirectory("camel-reload-kamelet-template"); templates = dir.resolve("templates.camel.yaml"); uses = dir.resolve("uses.camel.yaml"); plain = dir.resolve("plain.camel.yaml"); - Files.writeString(templates, """ - - routeTemplate: - id: "greeting" - parameters: - - name: "greeting" + Files.writeString(templates, template("{{greeting}}")); + Files.writeString(uses, """ + - route: + id: caller from: - uri: "direct:greet" + uri: direct:start steps: - - setBody: - constant: "{{greeting}}" - """); - Files.writeString(uses, """ - - templatedRoute: - routeTemplateRef: "greeting" - routeId: "greet" - parameters: - - name: "greeting" - value: "Hello" + - to: "kamelet:greeting?greeting=Hello" """); Files.writeString(plain, plainRoute("plain")); context.start(); loadRoutes(ResourceHelper.resolveResource(context, "file:" + templates), ResourceHelper.resolveResource(context, "file:" + uses), ResourceHelper.resolveResource(context, "file:" + plain)); + assertThat(greet()).isEqualTo("Hello"); + + strategy = new TestableStrategy(dir.toString()); + strategy.setCamelContext(context); + strategy.setPattern("*.yaml"); + // the strategy is not started (no file watcher in the test): its reload callback is driven by hand + strategy.doStart(); } @Override public void doCleanup() throws Exception { + if (strategy != null) { + strategy.doStop(); + } deleteRecursively(dir); } @Test - void routeRecordsItsSources() { - Route greet = context.getRoute("greet"); - assertThat(greet.getRouteTemplateId()).isEqualTo("greeting"); - assertThat(greet.getSourceResource().getLocation()).isEqualTo("file:" + templates); - assertThat(greet.getSourceLocation()).startsWith("file:" + templates + ":"); - assertThat(greet.getTemplatedRouteResource().getLocation()).isEqualTo("file:" + uses); - - Route other = context.getRoute("plain"); - assertThat(other.getRouteTemplateId()).isNull(); - assertThat(other.getSourceResource().getLocation()).isEqualTo("file:" + plain); - assertThat(other.getTemplatedRouteResource()).isNull(); + void reloadOfAnotherFileKeepsTheRouteUsingTheKamelet() throws Exception { + Files.writeString(plain, plainRoute("plain changed")); + reload(plain); + + assertThat(strategy.getFailedCounter()).isZero(); + assertThat(strategy.getLastError()).isNull(); + assertThat(context.getRouteController().getRouteStatus("caller")).isEqualTo(ServiceStatus.Started); + assertThat(greet()).isEqualTo("Hello"); + assertThat(context.createFluentProducerTemplate().to("direct:plain").request(String.class)) + .isEqualTo("plain changed"); } @Test - void reloadOfAnotherFileKeepsTheTemplatedRoute() throws Exception { - TestableStrategy strategy = new TestableStrategy(dir.toString()); - strategy.setCamelContext(context); - strategy.setPattern("*.yaml"); - // the strategy is not started (no file watcher in the test): its reload callback is driven by hand - 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.getRoutes()).hasSize(2); - assertThat(context.getRouteController().getRouteStatus("greet")).isEqualTo(ServiceStatus.Started); - assertThat(context.createFluentProducerTemplate().to("direct:greet").request(String.class)) - .isEqualTo("Hello"); - assertThat(context.createFluentProducerTemplate().to("direct:plain").request(String.class)) - .isEqualTo("plain changed"); - assertThat(context.getRoute("greet").getTemplatedRouteResource().getLocation()).isEqualTo("file:" + uses); - } finally { - strategy.doStop(); - } + void reloadOfTheRouteTemplateFileIsUsedByTheRouteUsingTheKamelet() throws Exception { + // the file with the route template is loaded before the route that uses it, also when it is the changed file + Files.writeString(templates, template("{{greeting}} World")); + reload(templates); + + assertThat(strategy.getFailedCounter()).isZero(); + assertThat(strategy.getLastError()).isNull(); + assertThat(greet()).isEqualTo("Hello World"); } @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(); + void failedReloadRestoresTheRouteUsingTheKamelet() throws Exception { + Files.writeString(plain, """ + - route: + id: plain + from: + uri: direct:plain + steps: + - noSuchEip: {} + """); + Exception failure = null; 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(); + reload(plain); + } catch (Exception e) { + failure = e; } + + // the reload fails and the routes that ran before are restored (CAMEL-24860), the route using the Kamelet too + assertThat(failure).isNotNull(); + assertThat(context.getRouteController().getRouteStatus("caller")).isEqualTo(ServiceStatus.Started); + assertThat(greet()).isEqualTo("Hello"); + assertThat(context.createFluentProducerTemplate().to("direct:plain").request(String.class)).isEqualTo("plain"); + } + + private void reload(Path file) throws Exception { + strategy.getResourceReload().onReload(file.toString(), ResourceHelper.resolveResource(context, "file:" + file)); + } + + private String greet() { + return context.createFluentProducerTemplate().to("direct:start").request(String.class); + } + + private static String template(String body) { + return """ + - routeTemplate: + id: "greeting" + parameters: + - name: "greeting" + from: + uri: "kamelet:source" + steps: + - setBody: + simple: "%s" + """.formatted(body); } private static String plainRoute(String body) { 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 114d2cf48074..7787d0d52caf 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 @@ -141,19 +141,9 @@ 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(); + // must not fail on the route template (CAMEL-25616), and keeps the route template, so the templated route is + // created again (CAMEL-25619) + useRouteTemplateFromJava(); TestableStrategy strategy = new TestableStrategy(dir.toString()); strategy.setCamelContext(context); @@ -168,11 +158,69 @@ class RouteReloadTemplatedRouteSourceTest extends YamlTestSupport { assertThat(strategy.getLastError()).isNull(); assertThat(context.createFluentProducerTemplate().to("direct:plain").request(String.class)) .isEqualTo("plain changed"); + assertThat(context.getRouteController().getRouteStatus("greet")).isEqualTo(ServiceStatus.Started); + assertThat(context.createFluentProducerTemplate().to("direct:greet").request(String.class)) + .isEqualTo("Hello"); + assertThat(context.getRoute("greet").getTemplatedRouteResource().getLocation()).isEqualTo("file:" + uses); + } finally { + strategy.doStop(); + } + } + + @Test + void failedReloadWithTheRouteTemplateFromJavaRestoresTheTemplatedRoute() throws Exception { + // the routes that ran before a failed reload are restored (CAMEL-24860): the templated route too, as the + // route template from Java is kept (CAMEL-25619) + useRouteTemplateFromJava(); + + TestableStrategy strategy = new TestableStrategy(dir.toString()); + strategy.setCamelContext(context); + strategy.setPattern("*.yaml"); + strategy.doStart(); + try { + Files.writeString(plain, """ + - route: + id: plain + from: + uri: direct:plain + steps: + - noSuchEip: {} + """); + Exception failure = null; + try { + strategy.getResourceReload().onReload(plain.toString(), + ResourceHelper.resolveResource(context, "file:" + plain)); + } catch (Exception e) { + failure = e; + } + + assertThat(failure).isNotNull(); + assertThat(context.getRouteController().getRouteStatus("greet")).isEqualTo(ServiceStatus.Started); + assertThat(context.createFluentProducerTemplate().to("direct:greet").request(String.class)) + .isEqualTo("Hello"); + assertThat(context.createFluentProducerTemplate().to("direct:plain").request(String.class)) + .isEqualTo("plain"); } finally { strategy.doStop(); } } + private void useRouteTemplateFromJava() throws Exception { + 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(); + assertThat(context.createFluentProducerTemplate().to("direct:greet").request(String.class)).isEqualTo("Hello"); + } + private static String plainRoute(String body) { return """ - route:
