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: