This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch camel-4.10.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.10.x by this push:
new 4049e3f0247 CAMEL-22141: camel-main: Fix duplicate route id detect
when you load multiple routes on startup (such as via camel-jbang) (#18288)
4049e3f0247 is described below
commit 4049e3f02475f60e9e721c2a8da1229f0e541443
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Jun 5 16:15:31 2025 +0200
CAMEL-22141: camel-main: Fix duplicate route id detect when you load
multiple routes on startup (such as via camel-jbang) (#18288)
---
.../apache/camel/FailedToCreateRouteException.java | 5 ++
.../camel/support/service/ServiceHelper.java | 1 -
.../camel/impl/engine/DefaultRoutesLoader.java | 9 ++++
.../org/apache/camel/impl/DefaultCamelContext.java | 5 ++
.../java/org/apache/camel/impl/DefaultModel.java | 5 ++
.../main/java/org/apache/camel/model/Model.java | 7 +++
.../org/apache/camel/issues/RouteIdClashTest.java | 54 ++++++++++++++++++++++
.../org/apache/camel/main/BaseMainSupport.java | 10 +++-
.../org/apache/camel/main/RoutesConfigurer.java | 53 ++++++++++++++++++++-
.../java/org/apache/camel/main/MainScan3Test.java | 43 +++++++++++++++++
.../apache/camel/main/scan3/Foo2RouteBuilder.java | 28 +++++++++++
.../apache/camel/main/scan3/Foo3RouteBuilder.java | 28 +++++++++++
.../apache/camel/main/scan3/FooRouteBuilder.java | 28 +++++++++++
.../support/FileWatcherResourceReloadStrategy.java | 6 ++-
14 files changed, 277 insertions(+), 5 deletions(-)
diff --git
a/core/camel-api/src/main/java/org/apache/camel/FailedToCreateRouteException.java
b/core/camel-api/src/main/java/org/apache/camel/FailedToCreateRouteException.java
index c0c7d8fc3b0..9a2a7b2d5e6 100644
---
a/core/camel-api/src/main/java/org/apache/camel/FailedToCreateRouteException.java
+++
b/core/camel-api/src/main/java/org/apache/camel/FailedToCreateRouteException.java
@@ -25,6 +25,11 @@ public class FailedToCreateRouteException extends
RuntimeCamelException {
private final String routeId;
+ public FailedToCreateRouteException(String routeId, String route, String
cause) {
+ super("Failed to create route " + routeId + ": " +
getRouteMessage(route) + " because of " + cause);
+ this.routeId = routeId;
+ }
+
public FailedToCreateRouteException(String routeId, String route,
Throwable cause) {
super("Failed to create route " + routeId + ": " +
getRouteMessage(route) + " because of " + getExceptionMessage(cause),
cause);
diff --git
a/core/camel-api/src/main/java/org/apache/camel/support/service/ServiceHelper.java
b/core/camel-api/src/main/java/org/apache/camel/support/service/ServiceHelper.java
index a435dc80b11..1c464db6af9 100644
---
a/core/camel-api/src/main/java/org/apache/camel/support/service/ServiceHelper.java
+++
b/core/camel-api/src/main/java/org/apache/camel/support/service/ServiceHelper.java
@@ -125,7 +125,6 @@ public final class ServiceHelper {
if (service != null) {
service.start();
}
-
}
/**
diff --git
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java
index 7fcd12ab306..7faf18c30ce 100644
---
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java
+++
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java
@@ -27,6 +27,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
+import org.apache.camel.FailedToStartRouteException;
import org.apache.camel.RouteConfigurationsBuilder;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.StaticService;
@@ -299,6 +300,14 @@ public class DefaultRoutesLoader extends ServiceSupport
implements RoutesLoader,
for (RoutesBuilder builder : builders) {
// update any existing routes
Set<String> ids =
builder.updateRoutesToCamelContext(getCamelContext());
+ // check we do not have duplicate route ids in this group of
resources
+ for (String id : ids) {
+ if (answer.contains(id)) {
+ throw new FailedToStartRouteException(
+ id,
+ "duplicate route id detected " + id + ". Please
correct ids to be unique among all your routes.");
+ }
+ }
answer.addAll(ids);
}
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 87675091e80..a9266608e1e 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
@@ -235,6 +235,11 @@ public class DefaultCamelContext extends
SimpleCamelContext implements ModelCame
model.addModelLifecycleStrategy(modelLifecycleStrategy);
}
+ @Override
+ public void removeModelLifecycleStrategy(ModelLifecycleStrategy
modelLifecycleStrategy) {
+ model.removeModelLifecycleStrategy(modelLifecycleStrategy);
+ }
+
@Override
public List<ModelLifecycleStrategy> getModelLifecycleStrategies() {
return model.getModelLifecycleStrategies();
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 024d02aa029..f767c9ea08e 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
@@ -103,6 +103,11 @@ public class DefaultModel implements Model {
}
}
+ @Override
+ public void removeModelLifecycleStrategy(ModelLifecycleStrategy
modelLifecycleStrategy) {
+ this.modelLifecycleStrategies.remove(modelLifecycleStrategy);
+ }
+
@Override
public List<ModelLifecycleStrategy> getModelLifecycleStrategies() {
return modelLifecycleStrategies;
diff --git
a/core/camel-core-model/src/main/java/org/apache/camel/model/Model.java
b/core/camel-core-model/src/main/java/org/apache/camel/model/Model.java
index e225426a324..f7b3b6c4a41 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/Model.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/Model.java
@@ -43,6 +43,13 @@ public interface Model {
*/
void addModelLifecycleStrategy(ModelLifecycleStrategy
modelLifecycleStrategy);
+ /**
+ * Removes the given model lifecycle strategy
+ *
+ * @param modelLifecycleStrategy the strategy
+ */
+ void removeModelLifecycleStrategy(ModelLifecycleStrategy
modelLifecycleStrategy);
+
/**
* Returns the model lifecycle strategies used to handle lifecycle
notifications
*
diff --git
a/core/camel-core/src/test/java/org/apache/camel/issues/RouteIdClashTest.java
b/core/camel-core/src/test/java/org/apache/camel/issues/RouteIdClashTest.java
new file mode 100644
index 00000000000..5f1d9445c46
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/issues/RouteIdClashTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.issues;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.FailedToStartRouteException;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Fail.fail;
+
+public class RouteIdClashTest extends ContextTestSupport {
+
+ @Override
+ public boolean isUseRouteBuilder() {
+ return false;
+ }
+
+ @Test
+ public void testClash() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:in1").routeId("myroute").to("mock:test1");
+ from("direct:in2").routeId("myroute2").to("mock:test2");
+ from("direct:in3").routeId("myroute").to("mock:test3");
+ }
+ });
+ try {
+ context.start();
+ fail();
+ } catch (FailedToStartRouteException e) {
+ Assertions.assertEquals("myroute", e.getRouteId());
+ Assertions.assertEquals(
+ "Failed to start route myroute because of duplicate id
detected: myroute. Please correct ids to be unique among all your routes.",
+ e.getMessage());
+ }
+ }
+}
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
index e072f4bf0a5..e72a8878a32 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java
@@ -102,6 +102,7 @@ import org.apache.camel.support.jsse.TrustAllTrustManager;
import org.apache.camel.support.jsse.TrustManagersParameters;
import org.apache.camel.support.scan.PackageScanHelper;
import org.apache.camel.support.service.BaseService;
+import org.apache.camel.support.service.ServiceHelper;
import org.apache.camel.support.startup.BacklogStartupStepRecorder;
import org.apache.camel.support.startup.EnvStartupCondition;
import org.apache.camel.support.startup.FileStartupCondition;
@@ -835,12 +836,17 @@ public abstract class BaseMainSupport extends BaseService
{
protected void configureRoutes(CamelContext camelContext) throws Exception
{
RoutesConfigurer configurer = doCommonRouteConfiguration(camelContext);
- configurer.configureRoutes(camelContext);
+ ServiceHelper.startService(configurer);
+ try {
+ configurer.configureRoutes(camelContext);
+ } finally {
+ ServiceHelper.stopService(configurer);
+ }
}
private RoutesConfigurer doCommonRouteConfiguration(CamelContext
camelContext) {
// then configure and add the routes
- RoutesConfigurer configurer = new RoutesConfigurer();
+ RoutesConfigurer configurer = new RoutesConfigurer(camelContext);
routesCollector.setIgnoreLoadingError(mainConfigurationProperties.isRoutesCollectorIgnoreLoadingError());
if (mainConfigurationProperties.isRoutesCollectorEnabled()) {
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java
b/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java
index 7e396d8a7f5..16414ae1e37 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/RoutesConfigurer.java
@@ -18,6 +18,7 @@ package org.apache.camel.main;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -25,10 +26,15 @@ import java.util.Set;
import java.util.StringJoiner;
import org.apache.camel.CamelContext;
+import org.apache.camel.FailedToCreateRouteException;
+import org.apache.camel.NonManagedService;
import org.apache.camel.RouteConfigurationsBuilder;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.StartupStep;
+import org.apache.camel.model.Model;
+import org.apache.camel.model.ModelLifecycleStrategySupport;
+import org.apache.camel.model.RouteDefinition;
import org.apache.camel.spi.CamelBeanPostProcessor;
import org.apache.camel.spi.ExtendedRoutesBuilderLoader;
import org.apache.camel.spi.ModelineFactory;
@@ -38,6 +44,7 @@ import org.apache.camel.spi.RoutesLoader;
import org.apache.camel.spi.StartupStepRecorder;
import org.apache.camel.support.OrderedComparator;
import org.apache.camel.support.PluginHelper;
+import org.apache.camel.support.service.ServiceSupport;
import org.apache.camel.util.AntPathMatcher;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.ObjectHelper;
@@ -49,9 +56,11 @@ import org.slf4j.LoggerFactory;
/**
* To configure routes using {@link RoutesCollector} which collects the routes
from various sources.
*/
-public class RoutesConfigurer {
+public class RoutesConfigurer extends ServiceSupport implements
NonManagedService {
private static final Logger LOG =
LoggerFactory.getLogger(RoutesConfigurer.class);
+ private final DuplicateRouteDetector detector = new
DuplicateRouteDetector();
+ private final CamelContext camelContext;
private RoutesCollector routesCollector;
private boolean ignoreLoadingError;
private CamelBeanPostProcessor beanPostProcessor;
@@ -64,6 +73,10 @@ public class RoutesConfigurer {
private String routesIncludePattern;
private String routesSourceDir;
+ public RoutesConfigurer(CamelContext camelContext) {
+ this.camelContext = camelContext;
+ }
+
public boolean isIgnoreLoadingError() {
return ignoreLoadingError;
}
@@ -152,6 +165,17 @@ public class RoutesConfigurer {
this.beanPostProcessor = beanPostProcessor;
}
+ @Override
+ protected void doStart() throws Exception {
+
camelContext.getCamelContextExtension().getContextPlugin(Model.class).addModelLifecycleStrategy(detector);
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ detector.clear();
+
camelContext.getCamelContextExtension().getContextPlugin(Model.class).removeModelLifecycleStrategy(detector);
+ }
+
/**
* Collects routes and rests from the various sources (like registry or
opinionated classpath locations) and injects
* (adds) these into the Camel context.
@@ -511,4 +535,31 @@ public class RoutesConfigurer {
return answer;
}
+ private static class DuplicateRouteDetector extends
ModelLifecycleStrategySupport {
+
+ private final Set<String> ids = new HashSet<>();
+
+ void clear() {
+ ids.clear();
+ }
+
+ @Override
+ public void onAddRouteDefinition(RouteDefinition definition) {
+ String id = definition.getRouteId();
+ // only detect explicit assigned ids
+ if (id == null || id.isEmpty()) {
+ return;
+ }
+ String prefix = definition.getNodePrefixId();
+ if (prefix == null) {
+ prefix = "";
+ }
+ String key = id + prefix;
+ if (!ids.add(key)) {
+ throw new FailedToCreateRouteException(
+ definition.getId(), definition.toString(),
+ "duplicate route id detected " + id + ". Please
correct ids to be unique among all your routes.");
+ }
+ }
+ }
}
diff --git
a/core/camel-main/src/test/java/org/apache/camel/main/MainScan3Test.java
b/core/camel-main/src/test/java/org/apache/camel/main/MainScan3Test.java
new file mode 100644
index 00000000000..3228ae68c6d
--- /dev/null
+++ b/core/camel-main/src/test/java/org/apache/camel/main/MainScan3Test.java
@@ -0,0 +1,43 @@
+/*
+ * 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.main;
+
+import org.apache.camel.FailedToCreateRouteException;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class MainScan3Test {
+
+ @Test
+ public void testScan3() {
+ Main main = new Main();
+ main.configure().withBasePackageScan("org.apache.camel.main.scan3");
+ try {
+ main.start();
+ fail();
+ } catch (FailedToCreateRouteException e) {
+ assertEquals("foo2", e.getRouteId());
+ assertTrue(e.getMessage().contains("because of duplicate route id
detected foo2"));
+ }
+
+ main.stop();
+ }
+
+}
diff --git
a/core/camel-main/src/test/java/org/apache/camel/main/scan3/Foo2RouteBuilder.java
b/core/camel-main/src/test/java/org/apache/camel/main/scan3/Foo2RouteBuilder.java
new file mode 100644
index 00000000000..9a8f465e019
--- /dev/null
+++
b/core/camel-main/src/test/java/org/apache/camel/main/scan3/Foo2RouteBuilder.java
@@ -0,0 +1,28 @@
+/*
+ * 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.main.scan3;
+
+import org.apache.camel.builder.RouteBuilder;
+
+public class Foo2RouteBuilder extends RouteBuilder {
+
+ @Override
+ public void configure() {
+ from("direct:start2").routeId("foo2")
+ .process("hello2");
+ }
+}
diff --git
a/core/camel-main/src/test/java/org/apache/camel/main/scan3/Foo3RouteBuilder.java
b/core/camel-main/src/test/java/org/apache/camel/main/scan3/Foo3RouteBuilder.java
new file mode 100644
index 00000000000..1185f052db6
--- /dev/null
+++
b/core/camel-main/src/test/java/org/apache/camel/main/scan3/Foo3RouteBuilder.java
@@ -0,0 +1,28 @@
+/*
+ * 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.main.scan3;
+
+import org.apache.camel.builder.RouteBuilder;
+
+public class Foo3RouteBuilder extends RouteBuilder {
+
+ @Override
+ public void configure() {
+ from("direct:start3").routeId("foo2") // duplicate on purpose
+ .process("hello3");
+ }
+}
diff --git
a/core/camel-main/src/test/java/org/apache/camel/main/scan3/FooRouteBuilder.java
b/core/camel-main/src/test/java/org/apache/camel/main/scan3/FooRouteBuilder.java
new file mode 100644
index 00000000000..8e13f2fcabe
--- /dev/null
+++
b/core/camel-main/src/test/java/org/apache/camel/main/scan3/FooRouteBuilder.java
@@ -0,0 +1,28 @@
+/*
+ * 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.main.scan3;
+
+import org.apache.camel.builder.RouteBuilder;
+
+public class FooRouteBuilder extends RouteBuilder {
+
+ @Override
+ public void configure() {
+ from("direct:start").routeId("foo")
+ .process("hello");
+ }
+}
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java
b/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java
index 9415307fe7f..59b38793aa7 100644
---
a/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java
+++
b/core/camel-support/src/main/java/org/apache/camel/support/FileWatcherResourceReloadStrategy.java
@@ -314,8 +314,12 @@ public class FileWatcherResourceReloadStrategy extends
ResourceReloadStrategySup
} catch (Exception e) {
setLastError(e);
incFailedCounter();
+ String msg = e.getMessage();
+ if (msg.endsWith(".")) {
+ msg = msg.substring(0, msg.length() - 1);
+ }
LOG.warn("Error reloading routes from file: {}
due to: {}. This exception is ignored.", name,
- e.getMessage(), e);
+ msg, e);
}
}
}