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 ff3dbb9236d3 CAMEL-23400: camel-test - Add route diagram dumper 
(#23085)
ff3dbb9236d3 is described below

commit ff3dbb9236d38138458f3e1dfc907f1554917810
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri May 8 15:09:05 2026 +0200

    CAMEL-23400: camel-test - Add route diagram dumper (#23085)
    
    * CAMEL-23400: camel-test - Add route diagram dumper
---
 .../camel/diagram/DefaultRouteDiagramDumper.java   |  12 +-
 .../apache/camel/diagram/RouteDiagramHelper.java   |   9 ++
 .../org/apache/camel/diagram/RouteDiagramTest.java |   5 +
 .../test/junit6/util/CamelContextTestHelper.java   |  14 ++-
 .../test/junit6/util/RouteDumperExtension.java     |  37 +++++--
 .../camel/test/main/junit6/CamelMainContext.java   |  12 ++
 .../camel/test/main/junit6/CamelMainExtension.java |  31 +++++-
 .../camel/test/main/junit6/CamelMainTest.java      |   9 ++
 .../src/main/docs/test-spring-junit6.adoc          |   1 +
 .../spring/junit6/CamelAnnotationsHandler.java     |  34 ++++++
 .../junit6/CamelSpringBootExecutionListener.java   |   2 +
 .../test/spring/junit6/EnableRouteDiagramDump.java |  42 +++++++
 .../test/spring/junit6/RouteDumpEventNotifier.java |  17 ++-
 .../impl/console/RouteStructureDevConsole.java     |   8 +-
 .../working-with-camel-core/pages/index.adoc       |   1 +
 .../ROOT/images/images/route-diagram-sample.png    | Bin 0 -> 18833 bytes
 docs/user-manual/modules/ROOT/nav.adoc             |   1 +
 docs/user-manual/modules/ROOT/pages/dsl.adoc       |   1 +
 docs/user-manual/modules/ROOT/pages/index.adoc     |   1 +
 .../modules/ROOT/pages/route-diagram.adoc          | 122 +++++++++++++++++++++
 20 files changed, 331 insertions(+), 28 deletions(-)

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 de63eaa9f09f..97a78d434494 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
@@ -37,9 +37,7 @@ import org.apache.camel.spi.RouteDiagramDumper;
 import org.apache.camel.spi.annotations.JdkService;
 import org.apache.camel.support.LoggerHelper;
 import org.apache.camel.support.service.ServiceSupport;
-import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.IOHelper;
-import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
 
@@ -93,14 +91,14 @@ public class DefaultRouteDiagramDumper extends 
ServiceSupport implements CamelCo
 
         // render by groups
         for (String group : groups) {
-            root = (JsonObject) dc.call(DevConsole.MediaType.JSON, 
Map.of("filter", group));
+            // do not include scheme in filter
+            filter = LoggerHelper.stripScheme(group);
+            root = (JsonObject) dc.call(DevConsole.MediaType.JSON, 
Map.of("filter", filter));
             var routes = RouteDiagramHelper.parseRoutes(root);
+
             BufferedImage image = renderImage(routes, theme.name(), 12, 180, 
"CODE");
-            // normalize as file name
-            String name = StringHelper.after(group, ":", group);
-            name = name.replace(' ', '-');
-            name = FileUtil.stripExt(name);
             folder.mkdirs();
+            String name = RouteDiagramHelper.extractSourceName(group);
             File f = new File(folder, name + ".png");
             ImageIO.write(image, "png", f);
         }
diff --git 
a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramHelper.java
 
b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramHelper.java
index 5a3fe8b5028e..f4558c8bd117 100644
--- 
a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramHelper.java
+++ 
b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramHelper.java
@@ -23,6 +23,7 @@ import 
org.apache.camel.diagram.RouteDiagramLayoutEngine.NodeInfo;
 import org.apache.camel.diagram.RouteDiagramLayoutEngine.RouteInfo;
 import org.apache.camel.support.LoggerHelper;
 import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
 import org.apache.camel.util.json.Jsoner;
@@ -88,6 +89,14 @@ public final class RouteDiagramHelper {
         if (source == null || source.isBlank()) {
             return null;
         }
+        source = source.replace(' ', '-');
+        if (source.startsWith("source:")) {
+            source = source.substring(7);
+            // skip middle packages
+            if (source.contains(".")) {
+                source = StringHelper.afterLast(source, ".");
+            }
+        }
         source = LoggerHelper.sourceNameOnly(source);
         source = LoggerHelper.stripScheme(source);
         source = FileUtil.stripPath(source);
diff --git 
a/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
 
b/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
index 4f5e5cd86dca..602fdbeb8d55 100644
--- 
a/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
+++ 
b/components/camel-diagram/src/test/java/org/apache/camel/diagram/RouteDiagramTest.java
@@ -811,6 +811,11 @@ class RouteDiagramTest {
         assertEquals("my-route.yaml", 
RouteDiagramHelper.extractSourceName("file:/path/to/my-route.yaml"));
     }
 
+    @Test
+    void testExtractSourceCompiledJava() {
+        assertEquals("MyRouteBuilder", 
RouteDiagramHelper.extractSourceName("source:com.foo.MyRouteBuilder"));
+    }
+
     @Test
     void testExtractSourceNameClasspath() {
         assertEquals("my-route.yaml", 
RouteDiagramHelper.extractSourceName("classpath:my-route.yaml"));
diff --git 
a/components/camel-test/camel-test-junit6/src/main/java/org/apache/camel/test/junit6/util/CamelContextTestHelper.java
 
b/components/camel-test/camel-test-junit6/src/main/java/org/apache/camel/test/junit6/util/CamelContextTestHelper.java
index 5992c6aa9bbf..6b528847a183 100644
--- 
a/components/camel-test/camel-test-junit6/src/main/java/org/apache/camel/test/junit6/util/CamelContextTestHelper.java
+++ 
b/components/camel-test/camel-test-junit6/src/main/java/org/apache/camel/test/junit6/util/CamelContextTestHelper.java
@@ -61,6 +61,11 @@ public final class CamelContextTestHelper {
      */
     public static final String ROUTE_DUMP_ENABLED = "CamelTestRouteDump";
 
+    /**
+     * JVM system property which can set the output directory
+     */
+    public static final String ROUTE_DUMP_DIR = "CamelTestRouteDumpDir";
+
     private static final Logger LOG = 
LoggerFactory.getLogger(CamelContextTestHelper.class);
 
     public static CamelContext createCamelContext(Registry registry) throws 
Exception {
@@ -297,11 +302,16 @@ public final class CamelContextTestHelper {
             if ("true".equals(p)) {
                 p = "xml"; // xml is default
             }
-            boolean valid = "xml".equals(p) || "yaml".equals(p) || 
"false".equals(p);
+            boolean valid = "xml".equals(p) || "yaml".equals(p) || 
"false".equals(p) || "png".equals(p);
             if (!valid) {
-                throw new IllegalArgumentException("RouteDump must be: xml, 
yaml, true, or false");
+                throw new IllegalArgumentException("RouteDump must be: xml, 
yaml, png, true, or false");
             }
         }
         return p;
     }
+
+    public static String getRouteDumpDir() {
+        return System.getProperty(ROUTE_DUMP_DIR);
+    }
+
 }
diff --git 
a/components/camel-test/camel-test-junit6/src/main/java/org/apache/camel/test/junit6/util/RouteDumperExtension.java
 
b/components/camel-test/camel-test-junit6/src/main/java/org/apache/camel/test/junit6/util/RouteDumperExtension.java
index 4c360a6a3c41..30c690ea7fe9 100644
--- 
a/components/camel-test/camel-test-junit6/src/main/java/org/apache/camel/test/junit6/util/RouteDumperExtension.java
+++ 
b/components/camel-test/camel-test-junit6/src/main/java/org/apache/camel/test/junit6/util/RouteDumperExtension.java
@@ -19,13 +19,9 @@ package org.apache.camel.test.junit6.util;
 import org.apache.camel.model.ModelCamelContext;
 import org.apache.camel.spi.DumpRoutesStrategy;
 import org.apache.camel.util.StringHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class RouteDumperExtension {
 
-    private static final Logger LOG = 
LoggerFactory.getLogger(RouteDumperExtension.class);
-
     private final ModelCamelContext context;
 
     public RouteDumperExtension(ModelCamelContext context) {
@@ -33,17 +29,36 @@ public class RouteDumperExtension {
     }
 
     public void dumpRoute(Class<?> testClass, String currentTestName, String 
format) throws Exception {
-        LOG.debug("Dumping Route");
+        if ("png".equals(format)) {
+            // png is route diagrams
+            dumpRouteDiagram();
+        } else {
+            // anything else is regular route dump as text
+            String className = testClass.getSimpleName();
+            String dir = CamelContextTestHelper.getRouteDumpDir();
+            if (dir == null) {
+                dir = "target/camel-route-dump";
+            }
+            String name = className + "-" + 
StringHelper.before(currentTestName, "(") + "." + format;
 
-        String className = testClass.getSimpleName();
-        String dir = "target/camel-route-dump";
-        String name = className + "-" + StringHelper.before(currentTestName, 
"(") + "." + format;
+            DumpRoutesStrategy drs = 
context.getCamelContextExtension().getContextPlugin(DumpRoutesStrategy.class);
+            drs.setOutput(dir + "/" + name);
+            drs.setInclude("*");
+            drs.setLog(false);
+            drs.setUriAsParameters(true);
+            drs.dumpRoutes(format);
+        }
+    }
 
+    private void dumpRouteDiagram() {
+        String dir = CamelContextTestHelper.getRouteDumpDir();
+        if (dir == null) {
+            dir = "target/camel-route-diagram";
+        }
         DumpRoutesStrategy drs = 
context.getCamelContextExtension().getContextPlugin(DumpRoutesStrategy.class);
-        drs.setOutput(dir + "/" + name);
+        drs.setOutput(dir);
         drs.setInclude("*");
         drs.setLog(false);
-        drs.setUriAsParameters(true);
-        drs.dumpRoutes(format);
+        drs.dumpRoutes("png");
     }
 }
diff --git 
a/components/camel-test/camel-test-main-junit6/src/main/java/org/apache/camel/test/main/junit6/CamelMainContext.java
 
b/components/camel-test/camel-test-main-junit6/src/main/java/org/apache/camel/test/main/junit6/CamelMainContext.java
index 882fb7b27653..18095f426b17 100644
--- 
a/components/camel-test/camel-test-main-junit6/src/main/java/org/apache/camel/test/main/junit6/CamelMainContext.java
+++ 
b/components/camel-test/camel-test-main-junit6/src/main/java/org/apache/camel/test/main/junit6/CamelMainContext.java
@@ -114,6 +114,10 @@ final class CamelMainContext implements 
ExtensionContext.Store.CloseableResource
          * The flag indicating whether JMX should be enabled.
          */
         private boolean useJmx;
+        /**
+         * The flag indicating whether CamelContext should auto start routes.
+         */
+        private boolean autoStartup = true;
 
         /**
          * Construct a {@code Builder} with the given extension context.
@@ -135,6 +139,11 @@ final class CamelMainContext implements 
ExtensionContext.Store.CloseableResource
             return this;
         }
 
+        Builder withAutoStartup(boolean autoStartup) {
+            this.autoStartup = autoStartup;
+            return this;
+        }
+
         /**
          * Build the {@code CamelMainContext} and its underlying Camel context 
based on the data extracted from the
          * annotation {@code CamelMainTest}.
@@ -149,6 +158,9 @@ final class CamelMainContext implements 
ExtensionContext.Store.CloseableResource
             configureShutdownTimeout(camelContext);
             configureDebuggerIfNeeded(camelContext);
             initCamelContext(camelContext);
+            if (camelContext.isAutoStartup()) {
+                camelContext.setAutoStartup(autoStartup);
+            }
             final CamelBeanPostProcessor beanPostProcessor = 
PluginHelper.getBeanPostProcessor(extendedCamelContext);
             for (Object instance : instances) {
                 initInstance(beanPostProcessor, instance);
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 a4b46d7dd602..99b7fbec9eee 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
@@ -104,6 +104,7 @@ final class CamelMainExtension
         }
         dumpRouteCoverageIfNeeded(context, time, currentTestName);
         dumpRouteIfNeeded(context, currentTestName);
+        dumpRouteDiagramIfNeeded(context);
     }
 
     /**
@@ -112,8 +113,10 @@ final class CamelMainExtension
      */
     private CamelMainContext createCamelMainContextAndStart(ExtensionContext 
context) {
         try {
+            boolean autoStartup = getRouteDumpDiagramFolder(context) == null; 
// do not auto startup if we dump diagram
             final CamelMainContext camelMainContext = 
CamelMainContext.builder(context)
                     .useJmx(useJmx(context) || isRouteCoverageEnabled(context) 
|| isCamelDebugPresent())
+                    .withAutoStartup(autoStartup)
                     .build();
             camelMainContext.start();
             return camelMainContext;
@@ -171,7 +174,10 @@ final class CamelMainExtension
             final Class<?> requiredTestClass = context.getRequiredTestClass();
             // In case of a {@code @Nested} test class, its name will be 
prefixed by the name of its outer classes
             String className = 
requiredTestClass.getName().substring(requiredTestClass.getPackageName().length()
 + 1);
-            String dir = "target/camel-route-dump";
+            String dir = CamelContextTestHelper.getRouteDumpDir();
+            if (dir == null) {
+                dir = "target/camel-route-dump";
+            }
             String ext = dump.toLowerCase();
             String name = String.format("%s-%s.%s", className, 
StringHelper.before(currentTestName, "("), ext);
 
@@ -186,6 +192,19 @@ final class CamelMainExtension
         }
     }
 
+    private void dumpRouteDiagramIfNeeded(ExtensionContext context) {
+        String folder = getRouteDumpDiagramFolder(context);
+        if (folder != null) {
+            LOG.info("Dumping route diagrams to: {}", folder);
+            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.dumpRoutes("png");
+        }
+    }
+
     /**
      * Indicates whether the route coverage is enabled according to the given 
extension context and the value of the
      * system property {@link 
org.apache.camel.test.junit6.util.CamelContextTestHelper#ROUTE_COVERAGE_ENABLED}.
@@ -217,6 +236,16 @@ final class CamelMainExtension
         return dump;
     }
 
+    private String getRouteDumpDiagramFolder(ExtensionContext context) {
+        String dir = 
context.getRequiredTestInstances().getAllInstances().get(0).getClass()
+                .getAnnotation(CamelMainTest.class).dumpRouteDiagramFolder();
+        if (dir != null && !dir.isBlank()) {
+            return dir;
+        } else {
+            return null;
+        }
+    }
+
     /**
      * Indicates whether JMX should be used during testing according to the 
given extension context.
      * <p/>
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 6acf7bcd042f..2736fd59300c 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
@@ -229,6 +229,15 @@ public @interface CamelMainTest {
      */
     String dumpRoute() default "";
 
+    /**
+     * Whether to dump visual route diagrams (dumped into files in 
target/camel-route-diagram).
+     * <p/>
+     * This allows to generate diagrams of your Camel routes as part of 
documentation.
+     * <p/>
+     * This requires having camel-diagram JAR on the classpath.
+     */
+    String dumpRouteDiagramFolder() default "";
+
     /**
      * Whether JMX should be used during testing.
      *
diff --git 
a/components/camel-test/camel-test-spring-junit6/src/main/docs/test-spring-junit6.adoc
 
b/components/camel-test/camel-test-spring-junit6/src/main/docs/test-spring-junit6.adoc
index 4add0c36291f..a867418d6e6c 100644
--- 
a/components/camel-test/camel-test-spring-junit6/src/main/docs/test-spring-junit6.adoc
+++ 
b/components/camel-test/camel-test-spring-junit6/src/main/docs/test-spring-junit6.adoc
@@ -204,6 +204,7 @@ The following annotations can be used with 
`camel-spring-junit6` unit testing.
 | `@DisableJmx` | Used for disabling JMX
 | `@EnableRouteCoverage` | Enables dumping route coverage statistics. The 
route coverage status is written as xml files in the 
`target/camel-route-coverage` directory after the test has finished. See more 
information at xref:manual::camel-report-maven-plugin.adoc[Camel Maven Report 
Plugin].
 | `@EnableRouteDump` | Enables dumping route. The route dump is written as xml 
or yaml files in the `target/camel-route-dump` directory after the test has 
finished.
+| `@EnableRouteDiagramDump` | Enable dumping route diagrams into the specified 
folder. This requires having camel-diagram on the classpath.
 | `@ExcludeRoutes` | Indicates if certain route builder classes should be 
excluded from package scan discovery
 | `@MockEndpoints` | Auto-mocking of endpoints whose URIs match the provided 
filter. For more information, see xref:manual::advice-with.adoc[Advice With].
 | `@MockEndpointsAndSkip` | Auto-mocking of endpoints whose URIs match the 
provided filter with the added provision that the endpoints are also skipped. 
For more information, see xref:manual::advice-with.adoc[Advice With].
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 669df1888ec1..0e601ef33d73 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
@@ -252,6 +252,34 @@ public final class CamelAnnotationsHandler {
         }
     }
 
+    /**
+     * Dumps the route diagram after test is executed
+     */
+    public static void handleRouteDiagramDump(
+            ConfigurableApplicationContext context, Class<?> testClass,
+            Function<CamelSpringTestHelper.DoToSpringCamelContextsStrategy, 
String> testMethod)
+            throws Exception {
+
+        String folder = null;
+        if (testClass.isAnnotationPresent(EnableRouteDiagramDump.class)) {
+            folder = 
testClass.getAnnotation(EnableRouteDiagramDump.class).folder();
+        }
+        if (folder != null && !folder.isBlank()) {
+            final String dir = folder;
+            CamelSpringTestHelper.doToSpringCamelContexts(context, new 
CamelSpringTestHelper.DoToSpringCamelContextsStrategy() {
+                @Override
+                public void execute(String contextName, SpringCamelContext 
camelContext) throws Exception {
+                    LOGGER.info("Dumping route diagrams to: {}", dir);
+                    DumpRoutesStrategy drs = 
camelContext.getCamelContextExtension().getContextPlugin(DumpRoutesStrategy.class);
+                    drs.setOutput(dir);
+                    drs.setInclude("*");
+                    drs.setLog(false);
+                    drs.dumpRoutes("png");
+                }
+            });
+        }
+    }
+
     public static void handleProvidesBreakpoint(ConfigurableApplicationContext 
context, Class<?> testClass) throws Exception {
         Collection<Method> methods = 
CamelSpringTestHelper.getAllMethods(testClass);
         final List<Breakpoint> breakpoints = new LinkedList<>();
@@ -510,8 +538,14 @@ public final class CamelAnnotationsHandler {
             }
         }
 
+        boolean dumpRouteDiagram = 
testClass.isAnnotationPresent(EnableRouteDiagramDump.class);
+
         if (!skip) {
             CamelSpringTestHelper.doToSpringCamelContexts(context, 
(contextName, camelContext) -> {
+                // when dumping route diagrams we should not auto-start the 
routes
+                if (dumpRouteDiagram) {
+                    camelContext.setAutoStartup(false);
+                }
                 if (!camelContext.isStarted()) {
                     LOGGER.info("Starting CamelContext with name [{}].", 
contextName);
                     camelContext.start();
diff --git 
a/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/CamelSpringBootExecutionListener.java
 
b/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/CamelSpringBootExecutionListener.java
index a794f130acf8..f6debee45c35 100644
--- 
a/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/CamelSpringBootExecutionListener.java
+++ 
b/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/CamelSpringBootExecutionListener.java
@@ -124,6 +124,8 @@ public class CamelSpringBootExecutionListener extends 
AbstractTestExecutionListe
             CamelAnnotationsHandler.handleRouteCoverageDump(context, 
testClass, s -> testName);
             // also dump route as either xml or yaml
             CamelAnnotationsHandler.handleRouteDump(context, testClass, s -> 
testName);
+            // and dump route diagrams
+            CamelAnnotationsHandler.handleRouteDiagramDump(context, testClass, 
s -> testName);
         }
     }
 }
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
new file mode 100644
index 000000000000..f53137b9a28b
--- /dev/null
+++ 
b/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/EnableRouteDiagramDump.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.spring.junit6;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * To enable dumping route diagrams into the specified folder.
+ *
+ * This requires having camel-diagram on the classpath.
+ */
+@Documented
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE })
+public @interface EnableRouteDiagramDump {
+
+    /**
+     * The folder to store the route diagrams
+     */
+    String folder();
+
+}
diff --git 
a/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/RouteDumpEventNotifier.java
 
b/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/RouteDumpEventNotifier.java
index 99d6d6afd56c..a7bd5ce36def 100644
--- 
a/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/RouteDumpEventNotifier.java
+++ 
b/components/camel-test/camel-test-spring-junit6/src/main/java/org/apache/camel/test/spring/junit6/RouteDumpEventNotifier.java
@@ -23,6 +23,7 @@ import org.apache.camel.spi.CamelEvent;
 import org.apache.camel.spi.CamelEvent.CamelContextStoppingEvent;
 import org.apache.camel.spi.DumpRoutesStrategy;
 import org.apache.camel.support.EventNotifierSupport;
+import org.apache.camel.test.junit6.util.CamelContextTestHelper;
 
 public class RouteDumpEventNotifier extends EventNotifierSupport {
 
@@ -49,13 +50,19 @@ public class RouteDumpEventNotifier extends 
EventNotifierSupport {
         CamelContext context = ((CamelContextStoppingEvent) 
event).getContext();
         String testName = testMethodName.apply(this);
 
-        String dir = "target/camel-route-dump";
-        String ext = format.toLowerCase();
-        String name = String.format("%s-%s.%s", testClassName, testName, ext);
+        String dir = CamelContextTestHelper.getRouteDumpDir();
+        if (dir == null) {
+            dir = "target/camel-route-dump";
+        }
 
         DumpRoutesStrategy drs = 
context.getCamelContextExtension().getContextPlugin(DumpRoutesStrategy.class);
-
-        drs.setOutput(dir + "/" + name);
+        if ("png".equals(format)) {
+            drs.setOutput(dir);
+        } else {
+            String ext = format.toLowerCase();
+            String name = String.format("%s-%s.%s", testClassName, testName, 
ext);
+            drs.setOutput(dir + "/" + name);
+        }
         drs.setInclude("*");
         drs.setLog(false);
         drs.setUriAsParameters(true);
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java
index 38efd35994bb..a4cf2c8f2549 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java
@@ -159,8 +159,12 @@ public class RouteStructureDevConsole extends 
AbstractDevConsole {
         }
 
         String uri = route.getInput().getLabel();
-        String loc = LoggerHelper.getLineNumberLoggerName(route);
-        String onlyName = LoggerHelper.sourceNameOnly(loc);
+        String loc = null;
+        if (route.getResource() != null) {
+            loc = 
LoggerHelper.sourceNameOnly(route.getResource().getLocation());
+            loc = LoggerHelper.stripScheme(loc);
+        }
+        String onlyName = loc != null ? LoggerHelper.sourceNameOnly(loc) : 
null;
         return PatternHelper.matchPattern(route.getRouteId(), filter)
                 || PatternHelper.matchPattern(uri, filter)
                 || PatternHelper.matchPattern(loc, filter)
diff --git a/docs/main/modules/working-with-camel-core/pages/index.adoc 
b/docs/main/modules/working-with-camel-core/pages/index.adoc
index 75d8d7731f27..0d6398a7fe81 100644
--- a/docs/main/modules/working-with-camel-core/pages/index.adoc
+++ b/docs/main/modules/working-with-camel-core/pages/index.adoc
@@ -39,6 +39,7 @@ If you have basic knowledge about _routes_, you can use the 
following guides to
 ** xref:manual::oncompletion.adoc[On Completion]
 ** xref:manual::Endpoint-dsl.adoc[Endpoint DSL]
 ** xref:manual::route-template.adoc[Route Template]
+** xref:manual::route-diagram.adoc[Visual Route Diagrams]
 ** xref:manual::using-propertyplaceholder.adoc[Using Property Placeholder]
 ** xref:manual::variables.adoc[Using Variables]
 
diff --git 
a/docs/user-manual/modules/ROOT/images/images/route-diagram-sample.png 
b/docs/user-manual/modules/ROOT/images/images/route-diagram-sample.png
new file mode 100644
index 000000000000..38b90972e375
Binary files /dev/null and 
b/docs/user-manual/modules/ROOT/images/images/route-diagram-sample.png differ
diff --git a/docs/user-manual/modules/ROOT/nav.adoc 
b/docs/user-manual/modules/ROOT/nav.adoc
index 8dc47cfba8dc..fe52dc760287 100644
--- a/docs/user-manual/modules/ROOT/nav.adoc
+++ b/docs/user-manual/modules/ROOT/nav.adoc
@@ -89,6 +89,7 @@
 ** xref:route-group.adoc[RouteGroup]
 ** xref:context-reload.adoc[ContextReload]
 ** xref:route-reload.adoc[RouteReload]
+** xref:route-diagram.adoc[Visual Route Diagrams]
 ** xref:route-template.adoc[RouteTemplate]
 ** xref:routes.adoc[Routes]
 ** xref:startup-condition.adoc[Startup Condition]
diff --git a/docs/user-manual/modules/ROOT/pages/dsl.adoc 
b/docs/user-manual/modules/ROOT/pages/dsl.adoc
index aa3ecd2b7cdd..d4ef4020811b 100644
--- a/docs/user-manual/modules/ROOT/pages/dsl.adoc
+++ b/docs/user-manual/modules/ROOT/pages/dsl.adoc
@@ -22,4 +22,5 @@ languages (DSL) as listed below:
 * xref:Endpoint-dsl.adoc[Endpoint DSL] for creating routes using type-safe 
Camel endpoints in Java.
 * xref:dataformat-dsl.adoc[DataFormat DSL] for type-safe Camel data formats in 
Java.
 * xref:route-template.adoc[Route Template] for creating reusable route 
templates.
+* xref:route-diagram.adoc[Route Diagram] for generating visual route diagrams 
for documentation purposes.
 * xref:route-reload.adoc[Route Reload] for hot-reloading routes in a running 
Camel application.
diff --git a/docs/user-manual/modules/ROOT/pages/index.adoc 
b/docs/user-manual/modules/ROOT/pages/index.adoc
index 2a2403e425dc..888cf66607f6 100644
--- a/docs/user-manual/modules/ROOT/pages/index.adoc
+++ b/docs/user-manual/modules/ROOT/pages/index.adoc
@@ -103,6 +103,7 @@ For a deeper and better understanding of Apache Camel, an 
xref:faq:what-is-camel
 * xref:context-reload.adoc[ContextReload]
 * xref:route-reload.adoc[RouteReload]
 * xref:route-template.adoc[RouteTemplate]
+* xref:route-diagram.adoc[Visual Route Diagrams]
 * xref:routes.adoc[Routes]
 * xref:stream-caching.adoc[Stream caching]
 * xref:threading-model.adoc[Threading Model]
diff --git a/docs/user-manual/modules/ROOT/pages/route-diagram.adoc 
b/docs/user-manual/modules/ROOT/pages/route-diagram.adoc
new file mode 100644
index 000000000000..aaac92225229
--- /dev/null
+++ b/docs/user-manual/modules/ROOT/pages/route-diagram.adoc
@@ -0,0 +1,122 @@
+= Route Diagram
+
+**Available as of Camel 4.21**
+
+The `camel-diagram` component provides a _route render_ functionality that can 
output visual diagrams
+of your Camel routes.
+
+This feature can be used to automatically generate route diagrams as part of 
building and testing your projects.
+
+For example as shown below:
+
+image::images/route-diagram-sample.png[Foo Camel Route Diagram]
+
+== Generating Route Diagrams with Camel JBang
+
+You can generate route diagrams with JBang such:
+
+[source,bash]
+----
+camel cmd route-diagram foo.yaml
+----
+
+It also works with Java source files:
+
+[source,bash]
+----
+camel cmd route-diagram MyRoute.java
+----
+
+And you can include multiple files:
+
+[source,bash]
+----
+camel cmd route-diagram MyRoute.java foo.yaml
+----
+
+TIP: See more options with `camel cmd route-diagram --help`.
+
+And if you run Camel JBang with `--console` then the developer console also 
comes with this functionality,
+by opening the link: http://localhost:8080/q/dev/route-diagram
+
+
+== Generating Route Diagrams with Camel Main
+
+This is done by adding `camel-diagram` as test scoped dependency, and then 
adding a special unit test that is responsible
+for generating the route diagrams and saving them to a specified folder.
+
+The following code shows how to do this.
+
+[source,java]
+----
+package org.apache.camel.example;
+
+import org.apache.camel.test.main.junit6.CamelMainTest;
+import org.junit.jupiter.api.Test;
+
+/**
+ * To dump route diagram only once
+ */
+@CamelMainTest(mainClass = MyApplication.class, dumpRouteDiagramFolder = "doc")
+class MainDiagramTest {
+
+    @Test
+    void empty() {
+        // empty test method
+    }
+
+}
+----
+
+So when you run `mvn test` then this unit test is executed (together with your 
other tests), and it's responsible
+for generating the route diagrams.
+
+The diagrams would then be stored in the `doc` folder, as specified in the 
`dumpRouteDiagramFolder` parameter
+on the `@CamelMainTest` annotation.
+
+
+== Generating Route Diagrams with Spring Boot
+
+This is done by adding `camel-diagram` as test scoped dependency, and then 
adding a special unit test that is responsible
+for generating the route diagrams and saving them to a specified folder.
+
+The following code shows how to do this.
+
+[source,java]
+----
+package sample.camel;
+
+import org.apache.camel.test.spring.junit6.CamelSpringBootTest;
+import org.apache.camel.test.spring.junit6.EnableRouteDiagramDump;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+/**
+ * To dump route diagram only once
+ */
+@CamelSpringBootTest
+@SpringBootTest(classes = MyCamelApplication.class)
+@EnableRouteDiagramDump(folder = "doc")
+public class DumpRouteDiagramTest {
+
+    @Test
+    public void empty() {
+        // noop
+    }
+
+}
+----
+
+So when you run `mvn test` then this unit test is executed (together with your 
other tests), and it's responsible
+for generating the route diagrams.
+
+The diagrams would then be stored in the `doc` folder, as specified in the 
`folder` parameter
+on the `@EnableRouteDiagramDump` annotation.
+
+
+== See Also
+
+See these examples:
+
+- https://github.com/apache/camel-examples/tree/main/main[Camel Main 
Standalone with route diagrams]
+- 
https://github.com/apache/camel-spring-boot-examples/tree/main/spring-boot[Spring
 Boot with route diagrams]


Reply via email to