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 596134474fd CAMEL-22141: camel-main: Fix duplicate route id detect 
when you load multiple routes on startup (such as via camel-jbang) (#18288)
596134474fd is described below

commit 596134474fd3013c486edb304623513b7d8cff88
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 064eabac97c..3549773d6cb 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 4b886590458..757ed18f023 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;
@@ -834,12 +835,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);
                             }
                         }
                     }

Reply via email to