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 c46145cf900c Add topology diagram generation to test annotations 
(#23708)
c46145cf900c is described below

commit c46145cf900ca069d56a37a1bf1bb1d5fffcd9dc
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Jun 2 17:10:42 2026 +0200

    Add topology diagram generation to test annotations (#23708)
    
    When using @EnableRouteDiagramDump or @CamelMainTest to generate
    route diagrams during mvn test, a topology diagram is now also
    generated by default showing how routes connect to each other.
    
    New annotation attributes:
    - topology (default true) - enable/disable topology diagram
    - topologyExternal (default true) - include external systems
    
    New SPI methods:
    - RouteDiagramDumper.dumpTopologyToFolder()
    - DumpRoutesStrategy topology/topologyExternal properties
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../camel/diagram/DefaultRouteDiagramDumper.java   | 14 ++++
 .../camel/test/main/junit6/CamelMainExtension.java |  4 ++
 .../camel/test/main/junit6/CamelMainTest.java      | 10 +++
 .../spring/junit6/CamelAnnotationsHandler.java     |  3 +
 .../test/spring/junit6/EnableRouteDiagramDump.java | 10 +++
 .../org/apache/camel/spi/DumpRoutesStrategy.java   | 18 +++++
 .../org/apache/camel/spi/RouteDiagramDumper.java   | 13 ++++
 .../impl/DefaultDumpRoutesStrategyConfigurer.java  |  9 +++
 .../camel/impl/DefaultDumpRoutesStrategy.java      | 25 +++++++
 .../modules/ROOT/pages/route-diagram.adoc          | 84 ++++++++++++++++++++++
 10 files changed, 190 insertions(+)

diff --git 
a/components/camel-diagram/src/main/java/org/apache/camel/diagram/DefaultRouteDiagramDumper.java
 
b/components/camel-diagram/src/main/java/org/apache/camel/diagram/DefaultRouteDiagramDumper.java
index f0ab7e3b228d..4c9af5b1c3d4 100644
--- 
a/components/camel-diagram/src/main/java/org/apache/camel/diagram/DefaultRouteDiagramDumper.java
+++ 
b/components/camel-diagram/src/main/java/org/apache/camel/diagram/DefaultRouteDiagramDumper.java
@@ -194,6 +194,20 @@ public class DefaultRouteDiagramDumper extends 
ServiceSupport implements CamelCo
         return renderAscii(routes, nodeWidth, nodeLabel, unicode, null, null);
     }
 
+    @Override
+    public void dumpTopologyToFolder(Theme theme, boolean external, File 
folder) throws IOException {
+        BufferedImage image = dumpTopologyAsImage(theme, false, 180, 12);
+        if (image != null) {
+            folder.mkdirs();
+            String name = camelContext.getName();
+            if (name == null || name.isBlank()) {
+                name = "camel";
+            }
+            File f = new File(folder, name + "-topology.png");
+            ImageIO.write(image, "png", f);
+        }
+    }
+
     @Override
     public String dumpTopologyAsAsciiArt(int nodeWidth, boolean unicode) {
         DevConsole dc = 
getCamelContext().getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class)
diff --git 
a/components/camel-test/camel-test-main-junit6/src/main/java/org/apache/camel/test/main/junit6/CamelMainExtension.java
 
b/components/camel-test/camel-test-main-junit6/src/main/java/org/apache/camel/test/main/junit6/CamelMainExtension.java
index 99b7fbec9eee..d7540fe305fc 100644
--- 
a/components/camel-test/camel-test-main-junit6/src/main/java/org/apache/camel/test/main/junit6/CamelMainExtension.java
+++ 
b/components/camel-test/camel-test-main-junit6/src/main/java/org/apache/camel/test/main/junit6/CamelMainExtension.java
@@ -196,11 +196,15 @@ final class CamelMainExtension
         String folder = getRouteDumpDiagramFolder(context);
         if (folder != null) {
             LOG.info("Dumping route diagrams to: {}", folder);
+            CamelMainTest annotation = 
context.getRequiredTestInstances().getAllInstances().get(0).getClass()
+                    .getAnnotation(CamelMainTest.class);
             final ModelCamelContext camelContext = 
getContextStore(context).get(CONTEXT, CamelMainContext.class).context();
             DumpRoutesStrategy drs = 
camelContext.getCamelContextExtension().getContextPlugin(DumpRoutesStrategy.class);
             drs.setOutput(folder);
             drs.setInclude("*");
             drs.setLog(false);
+            drs.setTopology(annotation.dumpRouteDiagramTopology());
+            
drs.setTopologyExternal(annotation.dumpRouteDiagramTopologyExternal());
             drs.dumpRoutes("png");
         }
     }
diff --git 
a/components/camel-test/camel-test-main-junit6/src/main/java/org/apache/camel/test/main/junit6/CamelMainTest.java
 
b/components/camel-test/camel-test-main-junit6/src/main/java/org/apache/camel/test/main/junit6/CamelMainTest.java
index 2736fd59300c..3bf1f8a77469 100644
--- 
a/components/camel-test/camel-test-main-junit6/src/main/java/org/apache/camel/test/main/junit6/CamelMainTest.java
+++ 
b/components/camel-test/camel-test-main-junit6/src/main/java/org/apache/camel/test/main/junit6/CamelMainTest.java
@@ -238,6 +238,16 @@ public @interface CamelMainTest {
      */
     String dumpRouteDiagramFolder() default "";
 
+    /**
+     * Whether to also dump the route topology diagram showing how routes 
connect to each other.
+     */
+    boolean dumpRouteDiagramTopology() default true;
+
+    /**
+     * Whether to include external systems (kafka, http, etc.) in the topology 
diagram.
+     */
+    boolean dumpRouteDiagramTopologyExternal() default true;
+
     /**
      * Whether JMX should be used during testing.
      *
diff --git 
a/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/CamelAnnotationsHandler.java
 
b/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/CamelAnnotationsHandler.java
index 0e601ef33d73..317b5a184efb 100644
--- 
a/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/CamelAnnotationsHandler.java
+++ 
b/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/CamelAnnotationsHandler.java
@@ -266,6 +266,7 @@ public final class CamelAnnotationsHandler {
         }
         if (folder != null && !folder.isBlank()) {
             final String dir = folder;
+            final EnableRouteDiagramDump annotation = 
testClass.getAnnotation(EnableRouteDiagramDump.class);
             CamelSpringTestHelper.doToSpringCamelContexts(context, new 
CamelSpringTestHelper.DoToSpringCamelContextsStrategy() {
                 @Override
                 public void execute(String contextName, SpringCamelContext 
camelContext) throws Exception {
@@ -274,6 +275,8 @@ public final class CamelAnnotationsHandler {
                     drs.setOutput(dir);
                     drs.setInclude("*");
                     drs.setLog(false);
+                    drs.setTopology(annotation.topology());
+                    drs.setTopologyExternal(annotation.topologyExternal());
                     drs.dumpRoutes("png");
                 }
             });
diff --git 
a/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/EnableRouteDiagramDump.java
 
b/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/EnableRouteDiagramDump.java
index f53137b9a28b..55f1a023882e 100644
--- 
a/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/EnableRouteDiagramDump.java
+++ 
b/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/EnableRouteDiagramDump.java
@@ -39,4 +39,14 @@ public @interface EnableRouteDiagramDump {
      */
     String folder();
 
+    /**
+     * Whether to also dump the route topology diagram showing how routes 
connect to each other.
+     */
+    boolean topology() default true;
+
+    /**
+     * Whether to include external systems (kafka, http, etc.) in the topology 
diagram.
+     */
+    boolean topologyExternal() default true;
+
 }
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/DumpRoutesStrategy.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/DumpRoutesStrategy.java
index cca41d324274..d60b3f59ce82 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/DumpRoutesStrategy.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/DumpRoutesStrategy.java
@@ -87,4 +87,22 @@ public interface DumpRoutesStrategy extends StaticService {
      */
     void setOutput(String output);
 
+    boolean isTopology();
+
+    /**
+     * Whether to also dump route topology diagram when dumping route 
diagrams. Default is true.
+     *
+     * @since 4.21
+     */
+    void setTopology(boolean topology);
+
+    boolean isTopologyExternal();
+
+    /**
+     * Whether to include external systems (kafka, http, etc.) in the topology 
diagram. Default is true.
+     *
+     * @since 4.21
+     */
+    void setTopologyExternal(boolean topologyExternal);
+
 }
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/RouteDiagramDumper.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/RouteDiagramDumper.java
index c7192e4f9822..d72b06614cc7 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/RouteDiagramDumper.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/RouteDiagramDumper.java
@@ -123,6 +123,19 @@ public interface RouteDiagramDumper {
         throw new UnsupportedOperationException();
     }
 
+    /**
+     * Dumps the route topology as a PNG file in the given folder
+     *
+     * @param theme    the coloring theme
+     * @param external whether to include external systems (kafka, http, etc.)
+     * @param folder   the folder to store the file
+     *
+     * @since          4.21
+     */
+    default void dumpTopologyToFolder(Theme theme, boolean external, File 
folder) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
     /**
      * Dumps the route topology as ASCII art or Unicode box-drawing text
      *
diff --git 
a/core/camel-core-engine/src/generated/java/org/apache/camel/impl/DefaultDumpRoutesStrategyConfigurer.java
 
b/core/camel-core-engine/src/generated/java/org/apache/camel/impl/DefaultDumpRoutesStrategyConfigurer.java
index 31eb69155d64..57ad1a9e246d 100644
--- 
a/core/camel-core-engine/src/generated/java/org/apache/camel/impl/DefaultDumpRoutesStrategyConfigurer.java
+++ 
b/core/camel-core-engine/src/generated/java/org/apache/camel/impl/DefaultDumpRoutesStrategyConfigurer.java
@@ -32,6 +32,9 @@ public class DefaultDumpRoutesStrategyConfigurer extends 
org.apache.camel.suppor
         case "output": target.setOutput(property(camelContext, 
java.lang.String.class, value)); return true;
         case "resolveplaceholders":
         case "resolvePlaceholders": 
target.setResolvePlaceholders(property(camelContext, boolean.class, value)); 
return true;
+        case "topology": target.setTopology(property(camelContext, 
boolean.class, value)); return true;
+        case "topologyexternal":
+        case "topologyExternal": 
target.setTopologyExternal(property(camelContext, boolean.class, value)); 
return true;
         case "uriasparameters":
         case "uriAsParameters": 
target.setUriAsParameters(property(camelContext, boolean.class, value)); return 
true;
         default: return false;
@@ -50,6 +53,9 @@ public class DefaultDumpRoutesStrategyConfigurer extends 
org.apache.camel.suppor
         case "output": return java.lang.String.class;
         case "resolveplaceholders":
         case "resolvePlaceholders": return boolean.class;
+        case "topology": return boolean.class;
+        case "topologyexternal":
+        case "topologyExternal": return boolean.class;
         case "uriasparameters":
         case "uriAsParameters": return boolean.class;
         default: return null;
@@ -69,6 +75,9 @@ public class DefaultDumpRoutesStrategyConfigurer extends 
org.apache.camel.suppor
         case "output": return target.getOutput();
         case "resolveplaceholders":
         case "resolvePlaceholders": return target.isResolvePlaceholders();
+        case "topology": return target.isTopology();
+        case "topologyexternal":
+        case "topologyExternal": return target.isTopologyExternal();
         case "uriasparameters":
         case "uriAsParameters": return target.isUriAsParameters();
         default: return null;
diff --git 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java
 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java
index 52683d13291a..78221765535e 100644
--- 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java
+++ 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java
@@ -84,6 +84,8 @@ public class DefaultDumpRoutesStrategy extends ServiceSupport 
implements DumpRou
     private boolean log = true;
     private String output;
     private String outputFileName;
+    private boolean topology = true;
+    private boolean topologyExternal = true;
 
     @Override
     public CamelContext getCamelContext() {
@@ -152,6 +154,22 @@ public class DefaultDumpRoutesStrategy extends 
ServiceSupport implements DumpRou
         this.uriAsParameters = uriAsParameters;
     }
 
+    public boolean isTopology() {
+        return topology;
+    }
+
+    public void setTopology(boolean topology) {
+        this.topology = topology;
+    }
+
+    public boolean isTopologyExternal() {
+        return topologyExternal;
+    }
+
+    public void setTopologyExternal(boolean topologyExternal) {
+        this.topologyExternal = topologyExternal;
+    }
+
     @Override
     public void dumpRoutes(String format) {
         if ("yaml".equalsIgnoreCase(format)) {
@@ -190,6 +208,13 @@ public class DefaultDumpRoutesStrategy extends 
ServiceSupport implements DumpRou
                 }
                 dumper.dumpRoutesToFolder(filter, 
RouteDiagramDumper.Theme.LIGHT, new File(folder));
             }
+            if (topology) {
+                if (log) {
+                    LOG.info("Dumping route topology diagram as PNG to folder: 
{}", folder != null ? folder : ".");
+                }
+                dumper.dumpTopologyToFolder(RouteDiagramDumper.Theme.LIGHT, 
topologyExternal,
+                        new File(folder != null ? folder : "."));
+            }
         } catch (IOException e) {
             LOG.warn("Error dumping routes diagrams. This exception is 
ignored.", e);
         }
diff --git a/docs/user-manual/modules/ROOT/pages/route-diagram.adoc 
b/docs/user-manual/modules/ROOT/pages/route-diagram.adoc
index aaac92225229..41fadbbfb221 100644
--- a/docs/user-manual/modules/ROOT/pages/route-diagram.adoc
+++ b/docs/user-manual/modules/ROOT/pages/route-diagram.adoc
@@ -114,6 +114,90 @@ The diagrams would then be stored in the `doc` folder, as 
specified in the `fold
 on the `@EnableRouteDiagramDump` annotation.
 
 
+== Route Topology Diagrams
+
+In addition to individual route diagrams, Camel can generate _route topology 
diagrams_ that show how
+routes connect to each other. While a route diagram shows the internal 
processors within a single route,
+a topology diagram gives you a bird's-eye view of your entire application — 
which routes send messages to
+which other routes, and optionally which external systems (Kafka, HTTP, 
databases, etc.) are involved.
+
+=== Topology vs Route Diagram
+
+- **Route Diagram**: Shows the internal structure of a single route — the 
processors, EIPs, and endpoints within it.
+- **Topology Diagram**: Shows connections _between_ routes — how routes are 
wired together via shared endpoints
+  like `direct`, `seda`, `kafka`, etc.
+
+=== External Endpoints
+
+External systems such as Kafka brokers, HTTP services, databases, and cloud 
services can be shown as
+separate nodes in the topology diagram. This makes it easy to see how your 
Camel routes interact with
+the outside world.
+
+=== Endpoint Identity
+
+Some components use query parameters to identify different destinations. For 
example, `couchbase:http://host:8091?bucket=orders`
+and `couchbase:http://host:8091?bucket=invoices` target different buckets. 
Camel uses `endpointIdentity` metadata
+on component parameters to correctly match endpoints — two URIs that differ 
only in identity parameters are treated
+as different destinations in the topology.
+
+=== Generating Topology Diagrams with Camel JBang
+
+When running with Camel JBang, you can generate topology diagrams via:
+
+[source,bash]
+----
+camel cmd route-topology
+----
+
+See xref:jbang-commands/camel-jbang-cmd-route-topology.adoc[] for more details.
+
+The developer console is also available at: 
`http://localhost:8080/q/dev/route-topology`
+
+=== Generating Topology Diagrams during `mvn test`
+
+When using `@CamelMainTest` or `@EnableRouteDiagramDump` to generate route 
diagrams, a topology diagram
+is also generated by default. The topology diagram is saved as 
`<context-name>-topology.png` in the
+same output folder.
+
+You can control topology generation with the following options:
+
+- `topology` — Whether to generate a topology diagram (default: `true`)
+- `topologyExternal` — Whether to include external systems in the topology 
(default: `true`)
+
+==== Camel Main Example
+
+[source,java]
+----
+@CamelMainTest(mainClass = MyApplication.class,
+    dumpRouteDiagramFolder = "doc",
+    dumpRouteDiagramTopology = true,
+    dumpRouteDiagramTopologyExternal = true)
+class MainDiagramTest {
+
+    @Test
+    void empty() {
+        // empty test method
+    }
+}
+----
+
+==== Spring Boot Example
+
+[source,java]
+----
+@CamelSpringBootTest
+@SpringBootTest(classes = MyCamelApplication.class)
+@EnableRouteDiagramDump(folder = "doc", topology = true, topologyExternal = 
true)
+public class DumpRouteDiagramTest {
+
+    @Test
+    public void empty() {
+        // noop
+    }
+}
+----
+
+
 == See Also
 
 See these examples:

Reply via email to