This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch did in repository https://gitbox.apache.org/repos/asf/camel.git
commit 7fe4e2ae35804a21754f82144f16a4cd4a301e4b Author: Claus Ibsen <[email protected]> AuthorDate: Sat Sep 20 11:35:42 2025 +0200 CAMEL-22420: camel-core - Detect duplicate processor ids within same route --- .../camel/model/ProcessorDefinitionHelper.java | 12 +++++------ .../apache/camel/model/RouteDefinitionHelper.java | 22 ++++++++++++++------ .../camel/management/ManagedDuplicateIdTest.java | 24 ++++++++++++++++++++++ 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinitionHelper.java b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinitionHelper.java index 1dd53f99079..1372486a803 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinitionHelper.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinitionHelper.java @@ -18,9 +18,7 @@ package org.apache.camel.model; import java.util.ArrayList; import java.util.Collection; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; import org.apache.camel.CamelContext; import org.apache.camel.NamedNode; @@ -180,15 +178,15 @@ public final class ProcessorDefinitionHelper { * Traverses the node, including its children (recursive), and gathers all the node ids. * * @param node the target node - * @param set set to store ids, if <tt>null</tt> a new set will be created + * @param set list to store ids, if <tt>null</tt> a new list will be created * @param onlyCustomId whether to only store custom assigned ids (ie. * {@link org.apache.camel.model.OptionalIdentifiedDefinition#hasCustomIdAssigned()} * @param includeAbstract whether to include abstract nodes (ie. * {@link org.apache.camel.model.ProcessorDefinition#isAbstract()} - * @return the set with the found ids. + * @return the list with the found ids. */ - public static Set<String> gatherAllNodeIds( - ProcessorDefinition<?> node, Set<String> set, boolean onlyCustomId, boolean includeAbstract) { + public static List<String> gatherAllNodeIds( + ProcessorDefinition<?> node, List<String> set, boolean onlyCustomId, boolean includeAbstract) { if (node == null) { return set; } @@ -199,7 +197,7 @@ public final class ProcessorDefinitionHelper { } if (set == null) { - set = new LinkedHashSet<>(); + set = new ArrayList<>(); } // add ourselves diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java index da1748462db..8645a0d35db 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java @@ -22,11 +22,14 @@ import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.apache.camel.CamelContext; import org.apache.camel.ErrorHandlerFactory; @@ -260,11 +263,9 @@ public final class RouteDefinitionHelper { * @return <tt>null</tt> if no duplicate id's detected, otherwise the first found duplicate id is returned. */ public static String validateUniqueIds(RouteDefinition target, List<RouteDefinition> routes, String prefixId) { - Set<String> routesIds = new LinkedHashSet<>(); - // gather all ids for the existing route, but only include custom ids, - // and no abstract ids - // as abstract nodes is cross-cutting functionality such as interceptors - // etc + List<String> routesIds = new ArrayList<>(); + // gather all ids for the existing route, but only include custom ids, and no abstract ids + // as abstract nodes is cross-cutting functionality such as interceptors etc for (RouteDefinition route : routes) { // skip target route as we gather ids in a separate set if (route == target) { @@ -275,8 +276,17 @@ public final class RouteDefinitionHelper { // gather all ids for the target route, but only include custom ids, and // no abstract ids as abstract nodes is cross-cutting functionality such as interceptors etc - Set<String> targetIds = new LinkedHashSet<>(); + List<String> targetIds = new ArrayList<>(); ProcessorDefinitionHelper.gatherAllNodeIds(target, targetIds, true, false); + // are there any duplicates processor ids in the target route + List<String> duplicates = targetIds.stream().collect(Collectors.groupingBy(Function.identity())) + .entrySet().stream() + .filter(e -> e.getValue().size() > 1) + .map(Map.Entry::getKey) + .toList(); + if (!duplicates.isEmpty()) { + return duplicates.get(0); + } // now check for clash with the target route for (String id : targetIds) { diff --git a/core/camel-management/src/test/java/org/apache/camel/management/ManagedDuplicateIdTest.java b/core/camel-management/src/test/java/org/apache/camel/management/ManagedDuplicateIdTest.java index 22dc06931eb..f5b4a847724 100644 --- a/core/camel-management/src/test/java/org/apache/camel/management/ManagedDuplicateIdTest.java +++ b/core/camel-management/src/test/java/org/apache/camel/management/ManagedDuplicateIdTest.java @@ -57,6 +57,30 @@ public class ManagedDuplicateIdTest extends ManagementTestSupport { } } + @Test + public void testDuplicateIdSingleRoute() throws Exception { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + from("direct:foo").routeId("foo") + .to("log:line").id("clash") + .to("log:foo").id("cheese") + .split(body()).id("mysplit") + .to("log:line").id("clash") + .end() + .to("mock:foo"); + } + }); + try { + context.start(); + fail("Should fail"); + } catch (Exception e) { + assertEquals( + "Failed to start route: foo because: Duplicate id detected: clash. Please correct ids to be unique among all your routes.", + e.getMessage()); + } + } + @Override public boolean isUseRouteBuilder() { return false;
