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(

Reply via email to