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 e5d85f3ac0d9 CAMEL-25196: camel-jbang - TUI Source tab route jump
links for XML DSL routes (#27144)
e5d85f3ac0d9 is described below
commit e5d85f3ac0d9183b16caeee430dbd3c052a71d69
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Sep 30 22:09:29 2026 +0200
CAMEL-25196: camel-jbang - TUI Source tab route jump links for XML DSL
routes (#27144)
…L routes
The route index reads Camel XML files with a stream reader (no DTD, no
external entities): the from of each route and what it sends to (to, toD,
wireTap, enrich, pollEnrich, and the case and otherwise of a switch), with
their lines, so XML routes link to and from the YAML and Java ones.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../jbang/core/commands/tui/JavaRouteScanner.java | 24 ++--
.../dsl/jbang/core/commands/tui/ScannedRoute.java | 30 +++++
.../dsl/jbang/core/commands/tui/SourceTab.java | 25 +++--
.../jbang/core/commands/tui/XmlRouteScanner.java | 124 +++++++++++++++++++++
.../src/main/resources/tui/help/source.md | 4 +-
.../core/commands/tui/JavaRouteScannerTest.java | 30 ++---
.../core/commands/tui/SourceTabJumpLinksTest.java | 31 ++++++
.../core/commands/tui/XmlRouteScannerTest.java | 79 +++++++++++++
8 files changed, 307 insertions(+), 40 deletions(-)
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/JavaRouteScanner.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/JavaRouteScanner.java
index c4569180ca64..4a0a5858fdff 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/JavaRouteScanner.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/JavaRouteScanner.java
@@ -48,14 +48,6 @@ final class JavaRouteScanner {
private static final int MAX_DEPTH = 50;
- /** A route: its id (null when it has none), its from endpoint and the
line of from, from 0. */
- record Route(String id, String fromUri, int line, List<To> tos) {
- }
-
- /** An endpoint a route sends to, and the line of the step, from 0. */
- record To(String uri, int line) {
- }
-
private JavaRouteScanner() {
}
@@ -69,24 +61,24 @@ final class JavaRouteScanner {
*
* @param javaSources the Java sources of the project by path, for the
constants of other classes
*/
- static List<Route> scan(String content, Map<String, Supplier<String>>
javaSources, CamelCatalog catalog) {
- List<Route> answer = new ArrayList<>();
+ static List<ScannedRoute> scan(String content, Map<String,
Supplier<String>> javaSources, CamelCatalog catalog) {
+ List<ScannedRoute> answer = new ArrayList<>();
for (RouteDefinition r : ProjectRoutes.parseJava(content, javaSources,
catalog).routes().getRoutes()) {
if (r.getInput() == null || r.getInput().getUri() == null) {
continue;
}
int line = r.getInput().getLineNumber() > 0 ?
r.getInput().getLineNumber() : r.getLineNumber();
- List<To> tos = new ArrayList<>();
+ List<ScannedRoute.To> tos = new ArrayList<>();
Set<Object> seen = Collections.newSetFromMap(new
IdentityHashMap<>());
for (ProcessorDefinition<?> p : r.getOutputs()) {
walk(p, tos, seen, 0);
}
- answer.add(new Route(r.getRouteId(), uri(r.getInput().getUri()),
Math.max(0, line - 1), tos));
+ answer.add(new ScannedRoute(r.getRouteId(),
uri(r.getInput().getUri()), Math.max(0, line - 1), tos));
}
return answer;
}
- private static void walk(ProcessorDefinition<?> p, List<To> tos,
Set<Object> seen, int depth) {
+ private static void walk(ProcessorDefinition<?> p, List<ScannedRoute.To>
tos, Set<Object> seen, int depth) {
if (p == null || depth > MAX_DEPTH || !seen.add(p)) {
return;
}
@@ -101,7 +93,7 @@ final class JavaRouteScanner {
uri = c.getExpression();
}
if (uri != null && p.getLineNumber() > 0) {
- tos.add(new To(uri(uri), p.getLineNumber() - 1));
+ tos.add(new ScannedRoute.To(uri(uri), p.getLineNumber() - 1));
}
if (p instanceof SwitchDefinition sw) {
// the destinations of a switch are its cases and fallback, not
outputs
@@ -123,9 +115,9 @@ final class JavaRouteScanner {
}
}
- private static void add(List<To> tos, String uri, int line) {
+ private static void add(List<ScannedRoute.To> tos, String uri, int line) {
if (uri != null && line > 0) {
- tos.add(new To(uri(uri), line - 1));
+ tos.add(new ScannedRoute.To(uri(uri), line - 1));
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ScannedRoute.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ScannedRoute.java
new file mode 100644
index 000000000000..c90309c248ff
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ScannedRoute.java
@@ -0,0 +1,30 @@
+/*
+ * 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.dsl.jbang.core.commands.tui;
+
+import java.util.List;
+
+/**
+ * A route as a source scanner of the Source tab reads it, for the jump links:
its id (null when it has none), its from
+ * endpoint and the line of from, from 0, and the endpoints it sends to.
+ */
+record ScannedRoute(String id, String fromUri, int line, List<ScannedRoute.To>
tos) {
+
+ /** An endpoint a route sends to, and the line of the step, from 0. */
+ record To(String uri, int line) {
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
index c097940799e6..1996afa2970e 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTab.java
@@ -1049,6 +1049,11 @@ class SourceTab extends AbstractTab {
}
if (SourceEditAssist.isYamlFile(path)) {
scanYamlRoutes(path, fromEntries, toEntries);
+ } else if
(path.getFileName().toString().toLowerCase().endsWith(".xml")) {
+ String content = readQuietly(path);
+ if (content != null) {
+ addRoutes(path, XmlRouteScanner.scan(content),
fromEntries, toEntries);
+ }
}
}
routeIndex = fromEntries;
@@ -1059,16 +1064,20 @@ class SourceTab extends AbstractTab {
private void scanJavaRoutes(
Path file, Map<String, Supplier<String>> javaSources,
List<RouteEntry> fromEntries, List<ToEntry> toEntries) {
String content = readQuietly(file);
- if (content == null) {
- return;
+ if (content != null) {
+ addRoutes(file, JavaRouteScanner.scan(content, javaSources,
ArchitectureView.catalog()), fromEntries, toEntries);
}
+ }
+
+ /** Adds the routes a scanner read from a file (Java or XML) to the index.
*/
+ private void addRoutes(Path file, List<ScannedRoute> routes,
List<RouteEntry> fromEntries, List<ToEntry> toEntries) {
String filePath = file.toString();
- for (JavaRouteScanner.Route route : JavaRouteScanner.scan(content,
javaSources, ArchitectureView.catalog())) {
+ for (ScannedRoute route : routes) {
int before = fromEntries.size();
- emitRouteEntry(fromEntries, route.id(), route.fromUri(), filePath,
route.line());
+ emitRouteEntry(fromEntries, route.id(),
JavaRouteScanner.uri(route.fromUri()), filePath, route.line());
String routeId = fromEntries.size() > before ?
fromEntries.get(before).routeId() : "";
- for (JavaRouteScanner.To to : route.tos()) {
- String toUri = stripQueryParams(to.uri());
+ for (ScannedRoute.To to : route.tos()) {
+ String toUri =
stripQueryParams(JavaRouteScanner.uri(to.uri()));
if (toUri != null && !toUri.isEmpty()) {
toEntries.add(new ToEntry(routeId, toUri, filePath,
to.line()));
}
@@ -1244,8 +1253,8 @@ class SourceTab extends AbstractTab {
fromUriToRoute.put(re.fromUri(), re);
}
- if (currentFilePath.endsWith(".java")) {
- // the steps of a Java route are in the index with their lines, as
the parser read them
+ if (currentFilePath.endsWith(".java") ||
currentFilePath.toLowerCase().endsWith(".xml")) {
+ // the steps of a Java or XML route are in the index with their
lines, as the parser read them
for (ToEntry te : toIndex) {
if (currentFilePath.equals(te.filePath())) {
RouteEntry target = fromUriToRoute.get(te.toUri());
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/XmlRouteScanner.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/XmlRouteScanner.java
new file mode 100644
index 000000000000..e1a7b59211a4
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/XmlRouteScanner.java
@@ -0,0 +1,124 @@
+/*
+ * 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.dsl.jbang.core.commands.tui;
+
+import java.io.StringReader;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.Set;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+/**
+ * The routes of a Camel XML source for the jump links of the Source tab
(CAMEL-25196): each route's from endpoint and
+ * the endpoints it sends to, with their lines. The XML is read as a stream,
without DTDs or external entities, and
+ * nothing of the project is loaded.
+ */
+final class XmlRouteScanner {
+
+ /** The steps whose uri attribute is an endpoint a route sends to. */
+ private static final Set<String> SENDING = Set.of("to", "toD", "wireTap",
"enrich", "pollEnrich");
+
+ private XmlRouteScanner() {
+ }
+
+ /** The routes of an XML source; empty when it has none or is not
well-formed. */
+ static List<ScannedRoute> scan(String content) {
+ List<ScannedRoute> answer = new ArrayList<>();
+ XMLInputFactory factory = XMLInputFactory.newFactory();
+ factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
+ factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES,
false);
+ XMLStreamReader xml = null;
+ try {
+ xml = factory.createXMLStreamReader(new StringReader(content));
+ Deque<String> open = new ArrayDeque<>();
+ String id = null;
+ String from = null;
+ int fromLine = 0;
+ List<ScannedRoute.To> tos = null;
+ // an enrich or pollEnrich whose endpoint is a <constant> below
it, and its line
+ int enrichLine = -1;
+ while (xml.hasNext()) {
+ int event = xml.next();
+ if (event == XMLStreamConstants.START_ELEMENT) {
+ String name = xml.getLocalName();
+ int line = Math.max(0, xml.getLocation().getLineNumber() -
1);
+ String parent = open.peek();
+ open.push(name);
+ if ("route".equals(name)) {
+ id = xml.getAttributeValue(null, "id");
+ from = null;
+ tos = new ArrayList<>();
+ enrichLine = -1;
+ } else if (tos == null) {
+ continue;
+ } else if ("from".equals(name) && from == null) {
+ from = xml.getAttributeValue(null, "uri");
+ fromLine = line;
+ } else if (SENDING.contains(name)) {
+ String uri = xml.getAttributeValue(null, "uri");
+ if (uri != null) {
+ tos.add(new ScannedRoute.To(uri, line));
+ } else if ("enrich".equals(name) ||
"pollEnrich".equals(name)) {
+ enrichLine = line;
+ }
+ } else if (("case".equals(name) ||
"otherwise".equals(name)) && "switch".equals(parent)) {
+ // the destinations of a switch
+ String uri = xml.getAttributeValue(null, "uri");
+ if (uri != null) {
+ tos.add(new ScannedRoute.To(uri, line));
+ }
+ } else if ("constant".equals(name) && enrichLine >= 0
+ && ("enrich".equals(parent) ||
"pollEnrich".equals(parent))) {
+ String uri = xml.getElementText().strip();
+ open.pop();
+ if (!uri.isEmpty()) {
+ tos.add(new ScannedRoute.To(uri, enrichLine));
+ }
+ enrichLine = -1;
+ }
+ } else if (event == XMLStreamConstants.END_ELEMENT) {
+ String name = open.isEmpty() ? null : open.pop();
+ if ("route".equals(name) && tos != null) {
+ if (from != null && !from.isBlank()) {
+ answer.add(new ScannedRoute(id, from.strip(),
fromLine, tos));
+ }
+ tos = null;
+ } else if ("enrich".equals(name) ||
"pollEnrich".equals(name)) {
+ enrichLine = -1;
+ }
+ }
+ }
+ } catch (XMLStreamException | RuntimeException e) {
+ // a file being edited may not be well-formed: what was read so
far is kept
+ } finally {
+ if (xml != null) {
+ try {
+ xml.close();
+ } catch (XMLStreamException e) {
+ // ignore
+ }
+ }
+ }
+ return answer;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md
index 73708455ab23..5766650940c2 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/resources/tui/help/source.md
@@ -74,7 +74,9 @@ Lines with `to:`, `toD:`, `wireTap:`, or similar endpoints
that reference
another route show a **↵ routeId** indicator. Press **Enter** on such a line
to jump to the target route's definition (within the same file or across
files).
Reverse links are shown on `from:` lines, indicating which route calls this
one.
-Jump indicators are hidden in plain mode.
+This works for YAML, XML and Java DSL routes, also across files of different
+DSLs; Java routes are read without compiling them. The case and otherwise of a
+switch link like a `to`. Jump indicators are hidden in plain mode.
## Go to Route
- **g** — open a filterable popup listing all routes found in the source files.
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/JavaRouteScannerTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/JavaRouteScannerTest.java
index 5fb57ba8389c..d9f3dff25be9 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/JavaRouteScannerTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/JavaRouteScannerTest.java
@@ -65,24 +65,24 @@ class JavaRouteScannerTest {
@Test
void routesAndTheirStepsWithLines() {
Map<String, Supplier<String>> sources = Map.of("/p/Orders.java", () ->
ORDERS, "/p/Endpoints.java", () -> ENDPOINTS);
- List<JavaRouteScanner.Route> routes = JavaRouteScanner.scan(ORDERS,
sources, new DefaultCamelCatalog());
+ List<ScannedRoute> routes = JavaRouteScanner.scan(ORDERS, sources, new
DefaultCamelCatalog());
-
assertThat(routes).extracting(JavaRouteScanner.Route::id).containsExactly("intake",
null);
- JavaRouteScanner.Route intake = routes.get(0);
+
assertThat(routes).extracting(ScannedRoute::id).containsExactly("intake", null);
+ ScannedRoute intake = routes.get(0);
assertThat(intake.fromUri()).isEqualTo("platform-http:/orders");
assertThat(intake.line()).isEqualTo(7);
// lines from 0, as the Source tab counts them; the constant of the
other class is resolved
assertThat(intake.tos()).containsExactly(
- new JavaRouteScanner.To("direct:vip", 10),
- new JavaRouteScanner.To("direct:standard", 12),
- new JavaRouteScanner.To("direct:audit?block=false", 14));
+ new ScannedRoute.To("direct:vip", 10),
+ new ScannedRoute.To("direct:standard", 12),
+ new ScannedRoute.To("direct:audit?block=false", 14));
- JavaRouteScanner.Route vip = routes.get(1);
+ ScannedRoute vip = routes.get(1);
assertThat(vip.fromUri()).isEqualTo("direct:vip");
assertThat(vip.line()).isEqualTo(16);
assertThat(vip.tos()).containsExactly(
- new JavaRouteScanner.To("kafka:vip-${header.region}", 18),
- new JavaRouteScanner.To("direct:audit", 20));
+ new ScannedRoute.To("kafka:vip-${header.region}", 18),
+ new ScannedRoute.To("direct:audit", 20));
}
@Test
@@ -94,10 +94,10 @@ class JavaRouteScannerTest {
}
}
""";
- List<JavaRouteScanner.Route> routes = JavaRouteScanner.scan(source,
Map.of(), new DefaultCamelCatalog());
+ List<ScannedRoute> routes = JavaRouteScanner.scan(source, Map.of(),
new DefaultCamelCatalog());
// direct://in as a route written as text has it, so it links to
from("direct:in") in another file
assertThat(routes.get(0).fromUri()).isEqualTo("direct:in");
- assertThat(routes.get(0).tos()).containsExactly(new
JavaRouteScanner.To("direct:out", 2));
+ assertThat(routes.get(0).tos()).containsExactly(new
ScannedRoute.To("direct:out", 2));
}
@Test
@@ -114,11 +114,11 @@ class JavaRouteScannerTest {
}
}
""";
- List<JavaRouteScanner.Route> routes = JavaRouteScanner.scan(source,
Map.of(), new DefaultCamelCatalog());
+ List<ScannedRoute> routes = JavaRouteScanner.scan(source, Map.of(),
new DefaultCamelCatalog());
assertThat(routes.get(0).tos()).containsExactly(
- new JavaRouteScanner.To("direct:billing", 4),
- new JavaRouteScanner.To("direct:technical", 5),
- new JavaRouteScanner.To("direct:review", 6));
+ new ScannedRoute.To("direct:billing", 4),
+ new ScannedRoute.To("direct:technical", 5),
+ new ScannedRoute.To("direct:review", 6));
}
@Test
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTabJumpLinksTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTabJumpLinksTest.java
index bae5283c492f..8effc58fae12 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTabJumpLinksTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SourceTabJumpLinksTest.java
@@ -128,6 +128,37 @@ class SourceTabJumpLinksTest {
assertThat(back.routeId()).isEqualTo("file:inbox");
}
+ @Test
+ void xmlRoutesLinkToAndFromTheOthers() throws Exception {
+ // CAMEL-25196: XML routes have the jump links of the YAML and Java
ones
+ Path orders = Files.writeString(dir.resolve("orders.xml"), """
+ <routes xmlns="http://camel.apache.org/schema/xml-io">
+ <route id="orders">
+ <from uri="file:inbox"/>
+ <to uri="direct:billing"/>
+ </route>
+ </routes>
+ """);
+ Path billing = Files.writeString(dir.resolve("billing.camel.yaml"), """
+ - route:
+ id: billing
+ from:
+ uri: direct:billing
+ steps:
+ - to:
+ uri: log:billing
+ """);
+ SourceTab tab = newTab();
+ assertThat(tab.loadDirectory(dir)).isTrue();
+
+ SourceViewer.JumpLink link = tab.computeJumpLinks(orders).get(3);
+ assertThat(link.routeId()).isEqualTo("billing");
+ assertThat(link.filePath()).isEqualTo(billing.toString());
+ SourceViewer.JumpLink back = tab.computeJumpLinks(billing).get(2);
+ assertThat(back.routeId()).isEqualTo("orders");
+ assertThat(back.targetLine()).isEqualTo(3);
+ }
+
private static SourceTab newTab() {
return new SourceTab(new MonitorContext(new
AtomicReference<>(List.of()), new AtomicReference<>(List.of())));
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/XmlRouteScannerTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/XmlRouteScannerTest.java
new file mode 100644
index 000000000000..806e3aca9eae
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/XmlRouteScannerTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.dsl.jbang.core.commands.tui;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class XmlRouteScannerTest {
+
+ @Test
+ void routesAndTheirStepsWithLines() {
+ List<ScannedRoute> routes = XmlRouteScanner.scan("""
+ <routes xmlns="http://camel.apache.org/schema/xml-io">
+ <route id="tickets">
+ <from uri="direct:tickets"/>
+ <choice>
+ <when>
+ <simple>${header.vip}</simple>
+ <to uri="direct:vip"/>
+ </when>
+ </choice>
+ <switch>
+ <selector><header>department</header></selector>
+ <case value="billing" uri="direct:billing"/>
+ <otherwise uri="direct:review"/>
+ </switch>
+ <enrich>
+ <constant>direct:customer</constant>
+ </enrich>
+ <wireTap uri="seda:audit?block=false"/>
+ </route>
+ <route>
+ <from uri="direct:vip"/>
+ <toD uri="kafka:vip-${header.region}"/>
+ </route>
+ </routes>
+ """);
+
assertThat(routes).extracting(ScannedRoute::id).containsExactly("tickets",
null);
+ ScannedRoute tickets = routes.get(0);
+ assertThat(tickets.fromUri()).isEqualTo("direct:tickets");
+ assertThat(tickets.line()).isEqualTo(2);
+ // lines from 0, as the Source tab counts them
+ assertThat(tickets.tos()).containsExactly(
+ new ScannedRoute.To("direct:vip", 6),
+ new ScannedRoute.To("direct:billing", 11),
+ new ScannedRoute.To("direct:review", 12),
+ new ScannedRoute.To("direct:customer", 14),
+ new ScannedRoute.To("seda:audit?block=false", 17));
+ assertThat(routes.get(1).tos()).containsExactly(new
ScannedRoute.To("kafka:vip-${header.region}", 21));
+ }
+
+ @Test
+ void notWellFormedOrWithoutRoutes() {
+ assertThat(XmlRouteScanner.scan("<routes><route><from
uri=\"direct:a\"/>")).isEmpty();
+ assertThat(XmlRouteScanner.scan("<beans/>")).isEmpty();
+ // no DTD, no external entities
+ assertThat(XmlRouteScanner.scan("""
+ <!DOCTYPE routes [<!ENTITY x SYSTEM "file:///etc/passwd">]>
+ <routes><route><from uri="direct:&x;"/></route></routes>
+ """)).allSatisfy(r ->
assertThat(r.fromUri()).doesNotContain("root"));
+ }
+}