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 869bfbc4cf6b CAMEL-24866: camel-support - route reload fails with
duplicate route id when a new file is added next to a file with several routes
869bfbc4cf6b is described below
commit 869bfbc4cf6b56e249fa7bc99d99eb2f061d1aca
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Sep 21 11:08:28 2026 +0200
CAMEL-24866: camel-support - route reload fails with duplicate route id
when a new file is added next to a file with several routes
CAMEL-24866
With a dev-mode app whose route file holds several routes, adding a second
route file to the directory made the reload stop all routes and then fail:
```
WARN FileWatcherResourceReloadStrategy : Error reloading routes from file:
.../hello.camel.yaml
org.apache.camel.FailedToStartRouteException: Failed to start route:
create-table because: duplicate route id detected create-table.
```
`RouteWatcherReloadStrategy.onRouteReload` collected the sources to load
again from every existing route, adding the route's source resource each time
and only skipping the changed resource. A file with N routes was added N times,
`DefaultRoutesLoader.updateRoutes` loaded it N times, and the second copy
tripped the duplicate id check. A change to an existing file was fine because
that file is the excluded one.
The fix adds a source once per location, with the `equalResourceLocation`
comparison the method already uses. Test: `RouteReloadNewFileTest` (fails with
the duplicate id error without the change). Found with the camel-jbang-mcp
server stepwise benchmark, where the model added a route file next to the
example's three-route file and the app went down.
---
.../camel/support/RouteWatcherReloadStrategy.java | 8 +-
.../camel/dsl/yaml/RouteReloadNewFileTest.groovy | 97 ++++++++++++++++++++++
2 files changed, 102 insertions(+), 3 deletions(-)
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 316078843636..300bb65eab77 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
@@ -263,7 +263,7 @@ public class RouteWatcherReloadStrategy extends
FileWatcherResourceReloadStrateg
// to the last working set
previousSources.forEach(rs -> {
// remember all the sources of the current routes (except the
updated)
- if (rs != null && !equalResourceLocation(resources, rs)) {
+ if (rs != null && !equalResourceLocation(resources, rs) &&
!equalResourceLocation(sources, rs)) {
sources.add(rs);
}
});
@@ -273,9 +273,11 @@ public class RouteWatcherReloadStrategy extends
FileWatcherResourceReloadStrateg
// should all existing routes be stopped and removed first?
if (removeAllRoutes) {
// remember all the sources of the current routes (except the
updated)
+ // (a file with several routes is one source: adding it once
per route would load it several
+ // times and fail on a duplicate route id, CAMEL-24866)
getCamelContext().getRoutes().forEach(r -> {
Resource rs = r.getSourceResource();
- if (rs != null && !equalResourceLocation(resources, rs)) {
+ if (rs != null && !equalResourceLocation(resources, rs) &&
!equalResourceLocation(sources, rs)) {
sources.add(rs);
}
});
@@ -288,7 +290,7 @@ public class RouteWatcherReloadStrategy extends
FileWatcherResourceReloadStrateg
if (resources != null) {
for (Resource resource : resources) {
- if (Files.exists(Paths.get(resource.getURI()))) {
+ if (Files.exists(Paths.get(resource.getURI())) &&
!equalResourceLocation(sources, resource)) {
sources.add(resource);
}
}
diff --git
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteReloadNewFileTest.groovy
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteReloadNewFileTest.groovy
new file mode 100644
index 000000000000..f0955596e36f
--- /dev/null
+++
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/RouteReloadNewFileTest.groovy
@@ -0,0 +1,97 @@
+/*
+ * 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 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 java.nio.file.Files
+import java.nio.file.Path
+
+/**
+ * CAMEL-24866: a new route file next to a file with several routes reloads
without a duplicate route id: the existing
+ * file is one source, whatever the number of routes it holds.
+ */
+class RouteReloadNewFileTest extends YamlTestSupport {
+
+ Path dir
+ Path shop
+
+ @Override
+ def doSetup() {
+ dir = Files.createTempDirectory("camel-reload")
+ shop = dir.resolve("shop.camel.yaml")
+ Files.writeString(shop, '''
+ - route:
+ id: orders
+ from:
+ uri: direct:orders
+ steps:
+ - to:
+ uri: mock:orders
+ - route:
+ id: report
+ from:
+ uri: direct:report
+ steps:
+ - to:
+ uri: mock:report
+ - route:
+ id: setup
+ from:
+ uri: direct:setup
+ steps:
+ - to:
+ uri: mock:setup
+ ''')
+ context.start()
+ loadRoutes(ResourceHelper.resolveResource(context, "file:" + shop))
+ }
+
+ def cleanup() {
+ dir.toFile().deleteDir()
+ }
+
+ def 'a new file reloads next to a file with several routes'() {
+ setup:
+ def strategy = new RouteWatcherReloadStrategy(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()
+ assert context.routes.size() == 3
+ when: 'a second file is added'
+ def hello = dir.resolve("hello.camel.yaml")
+ Files.writeString(hello, '''
+ - route:
+ id: hello
+ from:
+ uri: direct:hello
+ steps:
+ - to:
+ uri: mock:hello
+ ''')
+ strategy.getResourceReload().onReload(hello.toString(),
ResourceHelper.resolveResource(context, "file:" + hello))
+ then: 'the three routes of the first file and the new one run'
+ context.routes.size() == 4
+ ["orders", "report", "setup", "hello"].every {
context.getRouteController().getRouteStatus(it) == ServiceStatus.Started }
+ cleanup:
+ strategy.doStop()
+ }
+}