This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch topology-external-endpoints in repository https://gitbox.apache.org/repos/asf/camel.git
commit 58efeb62c943529519097952c68e3d7111152f1d Author: Claus Ibsen <[email protected]> AuthorDate: Tue Jun 2 11:29:20 2026 +0200 Filter external endpoints to exclude inter-route connections and add dashed edges External endpoints that serve as inter-route connections (e.g. kafka topics consumed by one route and produced to by another) are now filtered out. Only truly external endpoints remain: consumers where no route sends to that URI, and producers where no route consumes from that URI. Also adds dashed line rendering for external edges in the ASCII/Unicode topology renderer. Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../camel/diagram/TopologyAsciiRenderer.java | 11 +++++---- .../apache/camel/diagram/TopologyDiagramTest.java | 26 +++++----------------- .../camel/impl/DefaultRouteTopologyDumper.java | 20 ++++++++++++----- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/components/camel-diagram/src/main/java/org/apache/camel/diagram/TopologyAsciiRenderer.java b/components/camel-diagram/src/main/java/org/apache/camel/diagram/TopologyAsciiRenderer.java index 942fea5ff174..d824b264a8ea 100644 --- a/components/camel-diagram/src/main/java/org/apache/camel/diagram/TopologyAsciiRenderer.java +++ b/components/camel-diagram/src/main/java/org/apache/camel/diagram/TopologyAsciiRenderer.java @@ -44,6 +44,8 @@ public class TopologyAsciiRenderer { private static final char UNI_T_UP = '┴'; private static final char UNI_CROSS = '┼'; private static final char UNI_ARROW = '▼'; + private static final char UNI_DASH_V = '┆'; + private static final char UNI_DASH_H = '┄'; private final int nodeWidth; private final int boxWidth; @@ -242,8 +244,9 @@ public class TopologyAsciiRenderer { return; } - char v = unicode ? UNI_V : '|'; - char h = unicode ? UNI_H : '-'; + boolean dashed = "external".equals(edge.connectionType); + char v = dashed ? (unicode ? UNI_DASH_V : ':') : (unicode ? UNI_V : '|'); + char h = dashed ? (unicode ? UNI_DASH_H : '-') : (unicode ? UNI_H : '-'); char arrow = unicode ? UNI_ARROW : 'v'; if (fromCx == toCx) { @@ -400,11 +403,11 @@ public class TopologyAsciiRenderer { } private boolean isVertical(char ch) { - return ch == '|' || ch == UNI_V; + return ch == '|' || ch == UNI_V || ch == ':' || ch == UNI_DASH_V; } private boolean isHorizontal(char ch) { - return ch == '-' || ch == UNI_H; + return ch == '-' || ch == UNI_H || ch == UNI_DASH_H; } private void plotLine(char[][] grid, int row, int col, char ch) { diff --git a/components/camel-diagram/src/test/java/org/apache/camel/diagram/TopologyDiagramTest.java b/components/camel-diagram/src/test/java/org/apache/camel/diagram/TopologyDiagramTest.java index a58984ae38ac..d94497ef87ea 100644 --- a/components/camel-diagram/src/test/java/org/apache/camel/diagram/TopologyDiagramTest.java +++ b/components/camel-diagram/src/test/java/org/apache/camel/diagram/TopologyDiagramTest.java @@ -391,7 +391,9 @@ class TopologyDiagramTest { @Test void testOrderProcessingWithExternalEndpoints() { - // Full order processing topology with external endpoints + // Full order processing topology + // Only platform-http is truly external (no route sends to it). + // All kafka topics link routes internally, so they are NOT external. List<TopologyNodeInfo> nodes = new ArrayList<>( List.of( node("order-generator", "timer:orders", "trigger"), @@ -411,38 +413,22 @@ class TopologyDiagramTest { edge("order-dispatcher", "fulfillment", "kafka:fulfillment", "external"), edge("order-dispatcher", "notification", "kafka:notifications", "external"))); - // Consumers (external-in) + // Only platform-http is truly external (messages arrive from outside Camel) nodes.add(node("in-order-api", "platform-http:/api/orders", "external-in")); edges.add(edge("in-order-api", "order-api", "platform-http:/api/orders", "external")); - nodes.add(node("in-order-dispatcher", "kafka:orders", "external-in")); - edges.add(edge("in-order-dispatcher", "order-dispatcher", "kafka:orders", "external")); - nodes.add(node("in-fulfillment", "kafka:fulfillment", "external-in")); - edges.add(edge("in-fulfillment", "fulfillment", "kafka:fulfillment", "external")); - nodes.add(node("in-notification", "kafka:notifications", "external-in")); - edges.add(edge("in-notification", "notification", "kafka:notifications", "external")); - - // Producers (external-out) - nodes.add(node("out-process-order-0", "kafka:orders", "external-out")); - edges.add(edge("process-order", "out-process-order-0", "kafka:orders", "external")); - nodes.add(node("out-order-dispatcher-0", "kafka:fulfillment", "external-out")); - edges.add(edge("order-dispatcher", "out-order-dispatcher-0", "kafka:fulfillment", "external")); - nodes.add(node("out-order-dispatcher-1", "kafka:notifications", "external-out")); - edges.add(edge("order-dispatcher", "out-order-dispatcher-1", "kafka:notifications", "external")); TopologyLayoutEngine engine = new TopologyLayoutEngine(); TopologyLayoutResult result = engine.layout(nodes, edges); - // 7 routes + 4 consumers + 3 producers = 14 nodes - assertEquals(14, result.nodes.size()); + // 7 routes + 1 external consumer = 8 nodes + assertEquals(8, result.nodes.size()); // Verify three-band ordering TopologyLayoutNode extIn = findNode(result, "in-order-api"); TopologyLayoutNode route = findNode(result, "process-order"); - TopologyLayoutNode extOut = findNode(result, "out-process-order-0"); assertEquals(0, extIn.layer, "External-in should be at layer 0"); assertTrue(route.layer > extIn.layer, "Routes should be below external-in band"); - assertTrue(extOut.layer > route.layer, "External-out should be below route band"); } @Test diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultRouteTopologyDumper.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultRouteTopologyDumper.java index fe9ccf214345..ea855a05acf9 100644 --- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultRouteTopologyDumper.java +++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultRouteTopologyDumper.java @@ -102,21 +102,32 @@ public class DefaultRouteTopologyDumper implements RouteTopologyDumper { schemeRemoteMap.putIfAbsent(scheme, ep.isRemote()); } + // Collect all output URIs to determine which "from" endpoints are truly external + Set<String> allOutputUris = new HashSet<>(); + for (RouteDefinition rd : routeDefs) { + Collection<EndpointRequiredDefinition> outputs + = ProcessorDefinitionHelper.filterTypeInOutputs( + rd.getOutputs(), EndpointRequiredDefinition.class); + for (EndpointRequiredDefinition erd : outputs) { + allOutputUris.add(URISupport.stripQuery(erd.getEndpointUri())); + } + } + List<TopologyExternalEndpoint> externalEndpoints = new ArrayList<>(); Set<String> seenOutgoing = new HashSet<>(); for (RouteDefinition rd : routeDefs) { String routeId = rd.getRouteId(); - // Consumer (direction=in): each route has exactly 1 "from" endpoint + // Consumer (direction=in): only if no route sends to this URI (truly from outside Camel) String inputUri = URISupport.stripQuery(rd.getInput().getEndpointUri()); String inputScheme = extractScheme(inputUri); - if (isRemoteScheme(inputScheme, schemeRemoteMap)) { + if (isRemoteScheme(inputScheme, schemeRemoteMap) && !allOutputUris.contains(inputUri)) { externalEndpoints.add( new TopologyExternalEndpoint("in-" + routeId, inputUri, inputScheme, "in", routeId)); } - // Producers (direction=out): 0..N output endpoints per route + // Producers (direction=out): only if no route consumes from this URI (truly leaving Camel) Collection<EndpointRequiredDefinition> outputs = ProcessorDefinitionHelper.filterTypeInOutputs( rd.getOutputs(), EndpointRequiredDefinition.class); @@ -125,8 +136,7 @@ public class DefaultRouteTopologyDumper implements RouteTopologyDumper { for (EndpointRequiredDefinition erd : outputs) { String outputUri = URISupport.stripQuery(erd.getEndpointUri()); String outputScheme = extractScheme(outputUri); - if (isRemoteScheme(outputScheme, schemeRemoteMap)) { - // Deduplicate per route: same route sending to same URI only listed once + if (isRemoteScheme(outputScheme, schemeRemoteMap) && !inputUriToRouteIds.containsKey(outputUri)) { String dedupeKey = routeId + "|" + outputUri; if (seenOutgoing.add(dedupeKey)) { externalEndpoints.add(
