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]