This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch CAMEL-26823-topology-edge-reply
in repository https://gitbox.apache.org/repos/asf/camel.git

commit c1ea3046394bf73634f1f947efd98dfc23e8e950
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Nov 11 08:10:52 2026 +0100

    CAMEL-26823: camel-core - the route topology says whether a route waits for 
the answer of the route it sends to
    
    Each edge of the route topology gets reply: the route waits for the
    answer, as the send processor decides the exchange pattern (the
    exchangePattern of the endpoint uri, else the pattern of the send, else
    the pattern of the message the route consumed), or the component always
    runs the target first (direct, kamelet). A request-reply over a broker is
    then told from a hand-off. An error path never waits.
    
    A wireTap or a toD to an endpoint the route names, not one it computes, is
    an edge too (a wire tap never waits), and an external endpoint when no
    route consumes from it.
    
    TopologyEdge has the new reply component and keeps its seven-argument
    constructor; the route-topology console and the topology JSON of the dump
    routes strategy write it.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../apache/camel/catalog/dev-consoles-openapi.json |  9 +-
 .../camel/catalog/dev-consoles/route-topology.json |  9 +-
 .../org/apache/camel/spi/RouteTopologyDumper.java  | 16 +++-
 .../apache/camel/dev-console/route-topology.json   |  9 +-
 .../impl/console/RouteTopologyDevConsole.java      | 14 +++-
 .../impl/console/RouteTopologyDevConsoleTest.java  | 26 +++++-
 .../camel/impl/DefaultDumpRoutesStrategy.java      |  3 +
 .../camel/impl/DefaultRouteTopologyDumper.java     | 88 +++++++++++++++++--
 .../DefaultDumpRoutesStrategyTopologyJsonTest.java |  2 +
 .../impl/DefaultRouteTopologyDumperReplyTest.java  | 98 ++++++++++++++++++++++
 .../org/apache/camel/main/components.properties    | 10 ---
 .../org/apache/camel/main/dataformats.properties   |  2 -
 .../java/org/apache/camel/util/ArtifactUtils.java  | 12 ---
 .../java/org/apache/camel/util/SecurityUtils.java  |  1 -
 .../java/org/apache/camel/util/SensitiveUtils.java | 18 +---
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    | 12 +++
 16 files changed, 271 insertions(+), 58 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
index d87ddb449973..14a36d69d974 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles-openapi.json
@@ -7394,8 +7394,15 @@
                                                                                
                        "handling": {
                                                                                
                                "type": "string",
                                                                                
                                "description": "For an error path, what happens 
to the failure: handled (the message ends there), continued (the route goes on 
after it) or notHandled (the failure goes on to the caller); absent when a 
predicate decides it"
+                                                                               
                        },
+                                                                               
                        "reply": {
+                                                                               
                                "type": "boolean",
+                                                                               
                                "description": "Whether the source route waits 
for the answer of the target route: a call (direct) or a request-reply over a 
broker, rather than a hand-off"
                                                                                
                        }
-                                                                               
                }
+                                                                               
                },
+                                                                               
                "required": [
+                                                                               
                        "reply"
+                                                                               
                ]
                                                                                
        },
                                                                                
        "description": "The connections between nodes (only present when a 
route topology dumper is available)"
                                                                                
},
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-topology.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-topology.json
index 842512d8c341..976200187dbe 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-topology.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-topology.json
@@ -146,8 +146,15 @@
             "handling": {
               "type": "string",
               "description": "For an error path, what happens to the failure: 
handled (the message ends there), continued (the route goes on after it) or 
notHandled (the failure goes on to the caller); absent when a predicate decides 
it"
+            },
+            "reply": {
+              "type": "boolean",
+              "description": "Whether the source route waits for the answer of 
the target route: a call (direct) or a request-reply over a broker, rather than 
a hand-off"
             }
-          }
+          },
+          "required": [
+            "reply"
+          ]
         },
         "description": "The connections between nodes (only present when a 
route topology dumper is available)"
       },
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/RouteTopologyDumper.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/RouteTopologyDumper.java
index eb4a37340000..6ef8b42e6ed2 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/RouteTopologyDumper.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/RouteTopologyDumper.java
@@ -107,16 +107,28 @@ public interface RouteTopologyDumper {
      * @param handling       for an error path, what happens to the failure: 
{@link #HANDLING_HANDLED},
      *                       {@link #HANDLING_CONTINUED} or {@link 
#HANDLING_NOT_HANDLED}; null for a call, and when a
      *                       predicate decides it at runtime (since 4.23)
+     * @param reply          whether the source route waits for the answer of 
the target route: the send is InOut (the
+     *                       exchange pattern of the endpoint uri, else of the 
send, else of the message the route
+     *                       consumed), or the component always waits, as 
direct does: a request-reply over a broker
+     *                       rather than a hand-off (since 4.24)
      * @since                4.21
      */
     record TopologyEdge(String fromRouteId, String toRouteId, String endpoint, 
String connectionType, String kind,
-            String via, String handling) {
+            String via, String handling, boolean reply) {
 
         /**
          * An edge of kind {@link #EDGE_CALL}.
          */
         public TopologyEdge(String fromRouteId, String toRouteId, String 
endpoint, String connectionType) {
-            this(fromRouteId, toRouteId, endpoint, connectionType, EDGE_CALL, 
null, null);
+            this(fromRouteId, toRouteId, endpoint, connectionType, EDGE_CALL, 
null, null, false);
+        }
+
+        /**
+         * An edge without the answer of its target known.
+         */
+        public TopologyEdge(String fromRouteId, String toRouteId, String 
endpoint, String connectionType, String kind,
+                            String via, String handling) {
+            this(fromRouteId, toRouteId, endpoint, connectionType, kind, via, 
handling, false);
         }
 
         /**
diff --git 
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-topology.json
 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-topology.json
index 842512d8c341..976200187dbe 100644
--- 
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-topology.json
+++ 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-topology.json
@@ -146,8 +146,15 @@
             "handling": {
               "type": "string",
               "description": "For an error path, what happens to the failure: 
handled (the message ends there), continued (the route goes on after it) or 
notHandled (the failure goes on to the caller); absent when a predicate decides 
it"
+            },
+            "reply": {
+              "type": "boolean",
+              "description": "Whether the source route waits for the answer of 
the target route: a call (direct) or a request-reply over a broker, rather than 
a hand-off"
             }
-          }
+          },
+          "required": [
+            "reply"
+          ]
         },
         "description": "The connections between nodes (only present when a 
route topology dumper is available)"
       },
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteTopologyDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteTopologyDevConsole.java
index 6d57c50284cb..b179ba34413f 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteTopologyDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteTopologyDevConsole.java
@@ -66,7 +66,9 @@ public class RouteTopologyDevConsole extends 
AbstractDevConsole {
             @Metadata(description = "For an error path, what sends: 
errorHandler or onException") String via,
             @Metadata(description = "For an error path, what happens to the 
failure: handled (the message ends there),"
                                     + " continued (the route goes on after it) 
or notHandled (the failure goes on to the"
-                                    + " caller); absent when a predicate 
decides it") String handling) {
+                                    + " caller); absent when a predicate 
decides it") String handling,
+            @Metadata(description = "Whether the source route waits for the 
answer of the target route: a call (direct)"
+                                    + " or a request-reply over a broker, 
rather than a hand-off") boolean reply) {
     }
 
     public record ExternalEndpointEntry(
@@ -134,8 +136,12 @@ public class RouteTopologyDevConsole extends 
AbstractDevConsole {
                                 edge.toRouteId(), edge.endpoint(), 
edge.connectionType(), edge.via(),
                                 edge.handling() != null ? ", " + 
edge.handling() : ""));
                     } else {
-                        sb.append(String.format("    --> %s via %s [%s]%n",
-                                edge.toRouteId(), edge.endpoint(), 
edge.connectionType()));
+                        // a request-reply the component does not imply 
(direct always waits)
+                        boolean requestReply = edge.reply() && 
!edge.endpoint().startsWith("direct:")
+                                && !edge.endpoint().startsWith("kamelet:");
+                        sb.append(String.format("    --> %s via %s [%s%s]%n",
+                                edge.toRouteId(), edge.endpoint(), 
edge.connectionType(),
+                                requestReply ? ", request-reply" : ""));
                     }
                 }
             }
@@ -186,7 +192,7 @@ public class RouteTopologyDevConsole extends 
AbstractDevConsole {
         for (TopologyEdge edge : result.edges()) {
             edges.add(new EdgeEntry(
                     edge.fromRouteId(), edge.toRouteId(), edge.endpoint(), 
edge.connectionType(), edge.kind(),
-                    edge.via(), edge.handling()));
+                    edge.via(), edge.handling(), edge.reply()));
         }
 
         List<ExternalEndpointEntry> externalEndpoints = null;
diff --git 
a/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteTopologyDevConsoleTest.java
 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteTopologyDevConsoleTest.java
index f97c87fdd974..3be9d984f420 100644
--- 
a/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteTopologyDevConsoleTest.java
+++ 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteTopologyDevConsoleTest.java
@@ -54,6 +54,13 @@ class RouteTopologyDevConsoleTest extends ContextTestSupport 
{
 
                 from("direct:isolated").routeId("isolated")
                         .to("mock:nowhere");
+
+                // request-reply over a queue: the asker waits for the answer 
of the quoter
+                from("direct:ask").routeId("asker")
+                        .to("seda:quote?exchangePattern=InOut");
+
+                from("seda:quote").routeId("quoter")
+                        .to("mock:quoted");
             }
         };
     }
@@ -75,7 +82,7 @@ class RouteTopologyDevConsoleTest extends ContextTestSupport {
 
         JsonArray nodes = (JsonArray) json.get("nodes");
         assertNotNull(nodes);
-        assertEquals(5, nodes.size());
+        assertEquals(7, nodes.size());
 
         JsonArray edges = (JsonArray) json.get("edges");
         assertNotNull(edges);
@@ -110,6 +117,23 @@ class RouteTopologyDevConsoleTest extends 
ContextTestSupport {
         assertEquals("internal", edge.getString("connectionType"));
     }
 
+    @Test
+    void testAnEdgeSaysWhetherTheRouteWaitsForTheAnswer() {
+        DevConsole console = 
PluginHelper.getDevConsoleResolver(context).resolveDevConsole("route-topology");
+        JsonObject json = (JsonObject) console.call(DevConsole.MediaType.JSON);
+        JsonArray edges = (JsonArray) json.get("edges");
+
+        // CAMEL-26823: a direct route runs before the sender goes on, a 
request-reply waits for its answer
+        assertEquals(Boolean.TRUE, findEdge(edges, "trigger", 
"processor").get("reply"));
+        assertEquals(Boolean.TRUE, findEdge(edges, "asker", 
"quoter").get("reply"));
+        // a hand-off over a queue does not
+        assertEquals(Boolean.FALSE, findEdge(edges, "looper", 
"looper").get("reply"));
+
+        String text = (String) console.call(DevConsole.MediaType.TEXT);
+        assertTrue(text.contains("--> quoter via seda:quote [internal, 
request-reply]"), text);
+        assertTrue(text.contains("--> processor via direct:process 
[internal]"), text);
+    }
+
     @Test
     void testChainedConnections() {
         DevConsole console = 
PluginHelper.getDevConsoleResolver(context).resolveDevConsole("route-topology");
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 460928cd7603..110b3f594884 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
@@ -341,6 +341,9 @@ public class DefaultDumpRoutesStrategy extends 
ServiceSupport implements DumpRou
             if (edge.handling() != null) {
                 jo.put("handling", edge.handling());
             }
+            if (edge.reply()) {
+                jo.put("reply", true);
+            }
             edgesArr.add(jo);
         }
         root.put("edges", edgesArr);
diff --git 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultRouteTopologyDumper.java
 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultRouteTopologyDumper.java
index 444c295f8bac..c8c7e4c41209 100644
--- 
a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultRouteTopologyDumper.java
+++ 
b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultRouteTopologyDumper.java
@@ -28,6 +28,8 @@ import java.util.TreeMap;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Endpoint;
 import org.apache.camel.ErrorHandlerFactory;
+import org.apache.camel.ExchangePattern;
+import org.apache.camel.Route;
 import org.apache.camel.model.EndpointRequiredDefinition;
 import org.apache.camel.model.ExpressionSubElementDefinition;
 import org.apache.camel.model.Model;
@@ -35,12 +37,16 @@ import org.apache.camel.model.OnExceptionDefinition;
 import org.apache.camel.model.ProcessorDefinition;
 import org.apache.camel.model.ProcessorDefinitionHelper;
 import org.apache.camel.model.RouteDefinition;
+import org.apache.camel.model.SendDefinition;
+import org.apache.camel.model.ToDynamicDefinition;
+import org.apache.camel.model.WireTapDefinition;
 import org.apache.camel.model.errorhandler.DeadLetterChannelDefinition;
 import org.apache.camel.model.errorhandler.NoErrorHandlerDefinition;
 import org.apache.camel.model.language.ConstantExpression;
 import org.apache.camel.spi.EndpointUriFactory;
 import org.apache.camel.spi.RouteTopologyDumper;
 import org.apache.camel.spi.annotations.JdkService;
+import org.apache.camel.support.EndpointHelper;
 import org.apache.camel.util.URISupport;
 
 @JdkService(RouteTopologyDumper.FACTORY)
@@ -48,6 +54,8 @@ public class DefaultRouteTopologyDumper implements 
RouteTopologyDumper {
 
     private static final Set<String> TRIGGER_SCHEMES = Set.of("timer", 
"quartz", "cron", "scheduler");
     private static final Set<String> INTERNAL_SCHEMES = Set.of("direct", 
"seda", "vertx", "disruptor", "disruptor-vm");
+    // the schemes whose target runs before the route goes on, whatever the 
exchange pattern
+    private static final Set<String> WAITING_SCHEMES = Set.of("direct", 
"kamelet");
 
     @Override
     public TopologyResult dumpTopology(CamelContext context) {
@@ -88,14 +96,23 @@ public class DefaultRouteTopologyDumper implements 
RouteTopologyDumper {
                 Collection<EndpointRequiredDefinition> sends
                         = 
ProcessorDefinitionHelper.filterTypeInOutputs(List.of(output), 
EndpointRequiredDefinition.class);
                 for (EndpointRequiredDefinition erd : sends) {
+                    // an error path ends there: what it sends to is no answer 
the route waits for
+                    boolean reply = !onException && waitsForAnswer(context, 
rd, (ProcessorDefinition<?>) erd,
+                            erd.getEndpointUri());
                     addEdges(context, edges, inputUriToRouteIds, 
rd.getRouteId(), erd.getEndpointUri(), kind, via,
-                            handling);
+                            handling, reply);
+                }
+                // a toD or a wire tap to an endpoint the route names, not one 
it computes
+                for (ToDynamicDefinition td : staticDynamicSends(output)) {
+                    boolean reply = !onException && waitsForAnswer(context, 
rd, td, td.getUri());
+                    addEdges(context, edges, inputUriToRouteIds, 
rd.getRouteId(), td.getUri(), kind, via, handling,
+                            reply);
                 }
             }
             if (handlesFailures && eh instanceof DeadLetterChannelDefinition 
dlc && dlc.getDeadLetterUri() != null) {
                 // a dead letter channel handles the failure: the message ends 
at the dead letter uri
                 addEdges(context, edges, inputUriToRouteIds, rd.getRouteId(), 
dlc.getDeadLetterUri(), EDGE_ERROR,
-                        VIA_ERROR_HANDLER, HANDLING_HANDLED);
+                        VIA_ERROR_HANDLER, HANDLING_HANDLED, false);
             }
         }
 
@@ -107,16 +124,67 @@ public class DefaultRouteTopologyDumper implements 
RouteTopologyDumper {
 
     private static void addEdges(
             CamelContext context, List<TopologyEdge> edges, Map<String, 
List<String>> inputUriToRouteIds,
-            String fromRouteId, String uri, String kind, String via, String 
handling) {
+            String fromRouteId, String uri, String kind, String via, String 
handling, boolean reply) {
         String outputUri = normalizeUri(context, uri);
         List<String> targetRouteIds = inputUriToRouteIds.get(outputUri);
         if (targetRouteIds != null) {
             String scheme = extractScheme(outputUri);
             String connType = INTERNAL_SCHEMES.contains(scheme) ? "internal" : 
"external";
             for (String targetId : targetRouteIds) {
-                edges.add(new TopologyEdge(fromRouteId, targetId, outputUri, 
connType, kind, via, handling));
+                edges.add(new TopologyEdge(fromRouteId, targetId, outputUri, 
connType, kind, via, handling, reply));
+            }
+        }
+    }
+
+    /**
+     * Whether the route waits for the answer of what it sends to, as the send 
processor decides the exchange pattern:
+     * the exchangePattern of the endpoint uri, else the pattern of the send 
(to with pattern InOut, a wire tap is
+     * InOnly), else the pattern of the message the route consumed (InOut from 
platform-http, InOnly from a timer).
+     * Direct always runs the target before the route goes on.
+     */
+    static boolean waitsForAnswer(CamelContext context, RouteDefinition route, 
ProcessorDefinition<?> send, String uri) {
+        if (uri == null || send instanceof WireTapDefinition) {
+            // a wire tap sends a copy on another thread: the route goes on 
without its answer
+            return false;
+        }
+        if (WAITING_SCHEMES.contains(extractScheme(uri))) {
+            return true;
+        }
+        ExchangePattern pattern = null;
+        try {
+            pattern = 
EndpointHelper.resolveExchangePatternFromUrl(context.resolvePropertyPlaceholders(uri));
+        } catch (Exception e) {
+            // a uri that cannot be resolved says nothing
+        }
+        if (pattern == null) {
+            String sendPattern = send instanceof SendDefinition<?> sd ? 
sd.getPattern()
+                    : send instanceof ToDynamicDefinition td ? td.getPattern() 
: null;
+            if (sendPattern != null) {
+                pattern = 
context.getTypeConverter().tryConvertTo(ExchangePattern.class,
+                        context.resolvePropertyPlaceholders(sendPattern));
             }
         }
+        if (pattern == null) {
+            Route running = route.getRouteId() != null ? 
context.getRoute(route.getRouteId()) : null;
+            pattern = running != null && running.getEndpoint() != null ? 
running.getEndpoint().getExchangePattern() : null;
+        }
+        return pattern == ExchangePattern.InOut;
+    }
+
+    /**
+     * The toD and wire tap sends below a processor whose uri has no 
expression: the route names the endpoint (a wire
+     * tap to seda:audit), as a to does.
+     */
+    private static List<ToDynamicDefinition> 
staticDynamicSends(ProcessorDefinition<?> output) {
+        List<ToDynamicDefinition> answer = new ArrayList<>();
+        for (ToDynamicDefinition td : 
ProcessorDefinitionHelper.filterTypeInOutputs(List.of(output),
+                ToDynamicDefinition.class)) {
+            String uri = td.getUri();
+            if (uri != null && !uri.contains("${") && 
!uri.contains("$simple{")) {
+                answer.add(td);
+            }
+        }
+        return answer;
     }
 
     /**
@@ -172,6 +240,9 @@ public class DefaultRouteTopologyDumper implements 
RouteTopologyDumper {
             for (EndpointRequiredDefinition erd : outputs) {
                 allOutputUris.add(URISupport.stripQuery(erd.getEndpointUri()));
             }
+            for (ProcessorDefinition<?> output : rd.getOutputs()) {
+                staticDynamicSends(output).forEach(td -> 
allOutputUris.add(URISupport.stripQuery(td.getUri())));
+            }
         }
 
         List<TopologyExternalEndpoint> externalEndpoints = new ArrayList<>();
@@ -193,9 +264,14 @@ public class DefaultRouteTopologyDumper implements 
RouteTopologyDumper {
                     = ProcessorDefinitionHelper.filterTypeInOutputs(
                             rd.getOutputs(), EndpointRequiredDefinition.class);
 
+            List<String> outputUris = new ArrayList<>();
+            outputs.forEach(erd -> outputUris.add(erd.getEndpointUri()));
+            for (ProcessorDefinition<?> output : rd.getOutputs()) {
+                staticDynamicSends(output).forEach(td -> 
outputUris.add(td.getUri()));
+            }
             int outIdx = 0;
-            for (EndpointRequiredDefinition erd : outputs) {
-                String outputUri = URISupport.stripQuery(erd.getEndpointUri());
+            for (String uri : outputUris) {
+                String outputUri = URISupport.stripQuery(uri);
                 String outputScheme = extractScheme(outputUri);
                 if (isRemoteScheme(outputScheme, schemeRemoteMap) && 
!inputUriToRouteIds.containsKey(outputUri)) {
                     String dedupeKey = routeId + "|" + outputUri;
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/impl/DefaultDumpRoutesStrategyTopologyJsonTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/impl/DefaultDumpRoutesStrategyTopologyJsonTest.java
index a60ceccade36..7e08639cbe80 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/impl/DefaultDumpRoutesStrategyTopologyJsonTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/impl/DefaultDumpRoutesStrategyTopologyJsonTest.java
@@ -80,6 +80,8 @@ public class DefaultDumpRoutesStrategyTopologyJsonTest 
extends ContextTestSuppor
         assertThat(edge.getString("toRouteId")).isEqualTo("consumer");
         assertThat(edge.getString("endpoint")).isEqualTo("direct:linked");
         assertThat(edge.getString("connectionType")).isEqualTo("internal");
+        // CAMEL-26823: the producer waits for the consumer, as direct runs it 
before the producer goes on
+        assertThat(edge.get("reply")).isEqualTo(Boolean.TRUE);
     }
 
     @Test
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/impl/DefaultRouteTopologyDumperReplyTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/impl/DefaultRouteTopologyDumperReplyTest.java
new file mode 100644
index 000000000000..9971ea4af590
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/impl/DefaultRouteTopologyDumperReplyTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.impl;
+
+import java.util.List;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.ExchangePattern;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.RouteTopologyDumper;
+import org.apache.camel.spi.RouteTopologyDumper.TopologyEdge;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * CAMEL-26823: an edge says whether the route waits for the answer of the 
route it sends to, as the send processor
+ * decides the exchange pattern: the exchangePattern of the endpoint uri, else 
the pattern of the send, else the pattern
+ * of the message the route consumed; a direct route always runs before the 
sender goes on. The shape of
+ * connect-service/jms-request-reply, with seda in the place of the JMS broker.
+ */
+class DefaultRouteTopologyDumperReplyTest extends ContextTestSupport {
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("timer:orders?repeatCount=1").routeId("order-intake")
+                        // a hand-off: the order goes on a queue
+                        .to("seda:orders");
+
+                from("seda:orders").routeId("reserve-order")
+                        .errorHandler(deadLetterChannel("seda:orders-dead"))
+                        // request-reply: the endpoint asks for the answer
+                        .to("seda:stock?exchangePattern=InOut")
+                        // the send asks for it
+                        .to(ExchangePattern.InOut, "seda:pricing")
+                        // a call
+                        .to("direct:audit")
+                        // a copy, never an answer
+                        .wireTap("seda:metrics")
+                        // a hand-off to the subscribers
+                        .to("seda:reserved");
+
+                // the request of a client: what it sends on is InOut too
+                from("seda:quote?exchangePattern=InOut").routeId("quote")
+                        .to("seda:pricing");
+
+                from("seda:stock").routeId("stock-service").log("stock");
+                from("seda:pricing").routeId("pricing").log("pricing");
+                from("direct:audit").routeId("audit").log("audit");
+                from("seda:metrics").routeId("metrics").log("metrics");
+                from("seda:reserved").routeId("shipping").log("shipping");
+                from("seda:orders-dead").routeId("dead-orders").log("dead");
+            }
+        };
+    }
+
+    @Test
+    void anEdgeSaysWhetherTheRouteWaitsForTheAnswer() {
+        List<TopologyEdge> edges = new 
DefaultRouteTopologyDumper().dumpTopology(context).edges();
+
+        assertThat(edges).extracting(e -> e.fromRouteId() + "->" + 
e.toRouteId() + (e.reply() ? " waits" : ""))
+                .contains(
+                        "order-intake->reserve-order",
+                        "reserve-order->stock-service waits",
+                        "reserve-order->pricing waits",
+                        "reserve-order->audit waits",
+                        "reserve-order->metrics",
+                        "reserve-order->shipping",
+                        "quote->pricing waits");
+        // the dead letter channel: the message ends there
+        assertThat(edges).filteredOn(e -> "dead-orders".equals(e.toRouteId()))
+                .isNotEmpty().allMatch(e -> e.isErrorPath() && !e.reply());
+    }
+
+    @Test
+    void anEdgeOfBeforeWaitsForNoAnswer() {
+        assertThat(new TopologyEdge("a", "b", "seda:b", 
"internal").reply()).isFalse();
+        assertThat(new TopologyEdge("a", "b", "seda:b", "internal", 
RouteTopologyDumper.EDGE_CALL, null, null).reply())
+                .isFalse();
+    }
+}
diff --git 
a/core/camel-main/src/generated/resources/org/apache/camel/main/components.properties
 
b/core/camel-main/src/generated/resources/org/apache/camel/main/components.properties
index 7dd709b62764..e67614622a09 100644
--- 
a/core/camel-main/src/generated/resources/org/apache/camel/main/components.properties
+++ 
b/core/camel-main/src/generated/resources/org/apache/camel/main/components.properties
@@ -15,11 +15,9 @@ alibaba-sms
 amqp
 apicurio-registry
 arangodb
-as2
 asterisk
 atmosphere-websocket
 atom
-avro
 aws-bedrock
 aws-bedrock-agent
 aws-bedrock-agent-runtime
@@ -71,7 +69,6 @@ azure-storage-queue
 bean
 bean-validator
 bonita
-box
 braintree
 browse
 caffeine-cache
@@ -111,7 +108,6 @@ debezium-oracle
 debezium-postgres
 debezium-sqlserver
 dfdl
-dhis2
 direct
 disruptor
 disruptor-vm
@@ -129,7 +125,6 @@ elasticsearch
 elasticsearch-rest-client
 event
 exec
-fhir
 file
 file-watch
 flatpack
@@ -229,7 +224,6 @@ kafka
 kafka-share
 kamelet
 keycloak
-knative
 kserve
 kubernetes-config-maps
 kubernetes-cronjob
@@ -289,8 +283,6 @@ netty
 netty-http
 oaipmh
 odata
-olingo2
-olingo4
 once
 opa
 openai
@@ -335,13 +327,11 @@ robotframework
 rocketmq
 rss
 saga
-salesforce
 sap-netweaver
 scheduler
 schematron
 scp
 seda
-servicenow
 servlet
 sftp
 shell
diff --git 
a/core/camel-main/src/generated/resources/org/apache/camel/main/dataformats.properties
 
b/core/camel-main/src/generated/resources/org/apache/camel/main/dataformats.properties
index a6f67b25e6fd..0e7bff2ae6da 100644
--- 
a/core/camel-main/src/generated/resources/org/apache/camel/main/dataformats.properties
+++ 
b/core/camel-main/src/generated/resources/org/apache/camel/main/dataformats.properties
@@ -12,8 +12,6 @@ crypto
 csv
 dfdl
 fastjson
-fhirJson
-fhirXml
 flatpack
 fory
 grok
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java 
b/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
index 1a40f60a7320..1ea02fba1592 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
@@ -51,11 +51,9 @@ public final class ArtifactUtils {
             Map.<String, String> entry("amqp", "camel-amqp"),
             Map.<String, String> entry("apicurio-registry", 
"camel-apicurio-registry"),
             Map.<String, String> entry("arangodb", "camel-arangodb"),
-            Map.<String, String> entry("as2", "camel-as2"),
             Map.<String, String> entry("asterisk", "camel-asterisk"),
             Map.<String, String> entry("atmosphere-websocket", 
"camel-atmosphere-websocket"),
             Map.<String, String> entry("atom", "camel-atom"),
-            Map.<String, String> entry("avro", "camel-avro-rpc"),
             Map.<String, String> entry("aws-bedrock", "camel-aws-bedrock"),
             Map.<String, String> entry("aws-bedrock-agent", 
"camel-aws-bedrock"),
             Map.<String, String> entry("aws-bedrock-agent-runtime", 
"camel-aws-bedrock"),
@@ -107,7 +105,6 @@ public final class ArtifactUtils {
             Map.<String, String> entry("bean", "camel-bean"),
             Map.<String, String> entry("bean-validator", 
"camel-bean-validator"),
             Map.<String, String> entry("bonita", "camel-bonita"),
-            Map.<String, String> entry("box", "camel-box"),
             Map.<String, String> entry("braintree", "camel-braintree"),
             Map.<String, String> entry("browse", "camel-browse"),
             Map.<String, String> entry("caffeine-cache", "camel-caffeine"),
@@ -147,7 +144,6 @@ public final class ArtifactUtils {
             Map.<String, String> entry("debezium-postgres", 
"camel-debezium-postgres"),
             Map.<String, String> entry("debezium-sqlserver", 
"camel-debezium-sqlserver"),
             Map.<String, String> entry("dfdl", "camel-dfdl"),
-            Map.<String, String> entry("dhis2", "camel-dhis2"),
             Map.<String, String> entry("direct", "camel-direct"),
             Map.<String, String> entry("disruptor", "camel-disruptor"),
             Map.<String, String> entry("disruptor-vm", "camel-disruptor"),
@@ -165,7 +161,6 @@ public final class ArtifactUtils {
             Map.<String, String> entry("elasticsearch-rest-client", 
"camel-elasticsearch-rest-client"),
             Map.<String, String> entry("event", "camel-event"),
             Map.<String, String> entry("exec", "camel-exec"),
-            Map.<String, String> entry("fhir", "camel-fhir"),
             Map.<String, String> entry("file", "camel-file"),
             Map.<String, String> entry("file-watch", "camel-file-watch"),
             Map.<String, String> entry("flatpack", "camel-flatpack"),
@@ -265,7 +260,6 @@ public final class ArtifactUtils {
             Map.<String, String> entry("kafka-share", "camel-kafka-share"),
             Map.<String, String> entry("kamelet", "camel-kamelet"),
             Map.<String, String> entry("keycloak", "camel-keycloak"),
-            Map.<String, String> entry("knative", "camel-knative"),
             Map.<String, String> entry("kserve", "camel-kserve"),
             Map.<String, String> entry("kubernetes-config-maps", 
"camel-kubernetes"),
             Map.<String, String> entry("kubernetes-cronjob", 
"camel-kubernetes"),
@@ -325,8 +319,6 @@ public final class ArtifactUtils {
             Map.<String, String> entry("netty-http", "camel-netty-http"),
             Map.<String, String> entry("oaipmh", "camel-oaipmh"),
             Map.<String, String> entry("odata", "camel-odata"),
-            Map.<String, String> entry("olingo2", "camel-olingo2"),
-            Map.<String, String> entry("olingo4", "camel-olingo4"),
             Map.<String, String> entry("once", "camel-once"),
             Map.<String, String> entry("opa", "camel-opa"),
             Map.<String, String> entry("openai", "camel-openai"),
@@ -371,13 +363,11 @@ public final class ArtifactUtils {
             Map.<String, String> entry("rocketmq", "camel-rocketmq"),
             Map.<String, String> entry("rss", "camel-rss"),
             Map.<String, String> entry("saga", "camel-saga"),
-            Map.<String, String> entry("salesforce", "camel-salesforce"),
             Map.<String, String> entry("sap-netweaver", "camel-sap-netweaver"),
             Map.<String, String> entry("scheduler", "camel-scheduler"),
             Map.<String, String> entry("schematron", "camel-schematron"),
             Map.<String, String> entry("scp", "camel-jsch"),
             Map.<String, String> entry("seda", "camel-seda"),
-            Map.<String, String> entry("servicenow", "camel-servicenow"),
             Map.<String, String> entry("servlet", "camel-servlet"),
             Map.<String, String> entry("sftp", "camel-ftp"),
             Map.<String, String> entry("shell", "camel-shell"),
@@ -511,8 +501,6 @@ public final class ArtifactUtils {
             Map.<String, String> entry("csv", "camel-csv"),
             Map.<String, String> entry("dfdl", "camel-dfdl"),
             Map.<String, String> entry("fastjson", "camel-fastjson"),
-            Map.<String, String> entry("fhirJson", "camel-fhir"),
-            Map.<String, String> entry("fhirXml", "camel-fhir"),
             Map.<String, String> entry("flatpack", "camel-flatpack"),
             Map.<String, String> entry("fory", "camel-fory"),
             Map.<String, String> entry("grok", "camel-grok"),
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java 
b/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
index 511d1600f11c..694805268390 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/SecurityUtils.java
@@ -220,7 +220,6 @@ public final class SecurityUtils {
                 "component:xj",
                 "component:xsltsaxon"));
         owners.put("serializablepackages", Set.of(
-                "component:avro",
                 "dataformat:avro"));
         owners.put("skiptlsverify", Set.of(
                 "component:splunkhec"));
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/SensitiveUtils.java 
b/core/camel-util/src/main/java/org/apache/camel/util/SensitiveUtils.java
index ab94ce7cb3ab..10c5c3a26d26 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/SensitiveUtils.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/SensitiveUtils.java
@@ -91,26 +91,19 @@ public final class SensitiveUtils {
                     "knownhostsfile",
                     "knownhostsuri",
                     "mongodbpassword",
-                    "oauthclientid",
                     "oauthclientsecret",
                     "oauthtoken",
-                    "oauthtokenurl",
                     "p12filename",
                     "passphrase",
                     "passphrasebyte",
                     "password",
-                    "personalaccesstoken",
                     "postmanapikey",
                     "privatekey",
-                    "privatekeyfile",
                     "privatekeyname",
-                    "privatekeypassword",
                     "proxyauthpassword",
                     "proxyauthusername",
                     "proxypassword",
-                    "proxyuser",
                     "proxyusername",
-                    "publickeyid",
                     "publishkey",
                     "queueownerawsaccountid",
                     "realm",
@@ -143,7 +136,6 @@ public final class SensitiveUtils {
                     "user",
                     "userauthenticationcredentials",
                     "username",
-                    "userpassword",
                     "verificationcode",
                     "webhookverifytoken",
                     "zookeeperpassword"
@@ -206,26 +198,19 @@ public final class SensitiveUtils {
                                                     + "|\\Qknownhostsfile\\E"
                                                     + "|\\Qknownhostsuri\\E"
                                                     + "|\\Qmongodbpassword\\E"
-                                                    + "|\\Qoauthclientid\\E"
                                                     + 
"|\\Qoauthclientsecret\\E"
                                                     + "|\\Qoauthtoken\\E"
-                                                    + "|\\Qoauthtokenurl\\E"
                                                     + "|\\Qp12filename\\E"
                                                     + "|\\Qpassphrase\\E"
                                                     + "|\\Qpassphrasebyte\\E"
                                                     + "|\\Qpassword\\E"
-                                                    + 
"|\\Qpersonalaccesstoken\\E"
                                                     + "|\\Qpostmanapikey\\E"
                                                     + "|\\Qprivatekey\\E"
-                                                    + "|\\Qprivatekeyfile\\E"
                                                     + "|\\Qprivatekeyname\\E"
-                                                    + 
"|\\Qprivatekeypassword\\E"
                                                     + 
"|\\Qproxyauthpassword\\E"
                                                     + 
"|\\Qproxyauthusername\\E"
                                                     + "|\\Qproxypassword\\E"
-                                                    + "|\\Qproxyuser\\E"
                                                     + "|\\Qproxyusername\\E"
-                                                    + "|\\Qpublickeyid\\E"
                                                     + "|\\Qpublishkey\\E"
                                                     + 
"|\\Qqueueownerawsaccountid\\E"
                                                     + "|\\Qrealm\\E"
@@ -258,11 +243,10 @@ public final class SensitiveUtils {
                                                     + "|\\Quser\\E"
                                                     + 
"|\\Quserauthenticationcredentials\\E"
                                                     + "|\\Qusername\\E"
-                                                    + "|\\Quserpassword\\E"
                                                     + "|\\Qverificationcode\\E"
                                                     + 
"|\\Qwebhookverifytoken\\E"
                                                     + 
"|\\Qzookeeperpassword\\E"
-    // SENSITIVE-PATTERN: END
+                                                    // SENSITIVE-PATTERN: END
     ;
 
     private SensitiveUtils() {
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index fbb57a05ed5f..bde89e116481 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -256,6 +256,18 @@ in its model, so clearing them on the route saved little 
memory at the cost of m
 Applications that called `Route.clearRouteModel()` directly can remove the 
call. Implementations of the `Route` interface
 that override `clearRouteModel()` can remove the override.
 
+=== camel-core - the route topology says whether a route waits for the answer, 
and has the edges of a wire tap
+
+Each edge of the route topology (the `route-topology` developer console, the 
topology JSON of the dump routes strategy
+and `RouteTopologyDumper`) now says whether the route waits for the answer of 
the route it sends to: `reply` is `true`
+for a send to `direct`, and for a send that is InOut, as a request-reply over 
a JMS queue is
+(`exchangePattern=InOut`). The topology JSON writes `reply` only when it is 
`true`, and the text output of the console
+marks a request-reply with `request-reply`. The 
`RouteTopologyDumper.TopologyEdge` record has the new `reply`
+component; its seven-argument constructor is kept and creates an edge without 
it.
+
+A `wireTap` or a `toD` to an endpoint the route names, rather than one it 
computes with an expression, is now an edge
+too (a wire tap never waits for the answer), and an external endpoint when no 
route consumes from it.
+
 === camel-management - LastExchangeCompletedExchangeId is updated at most once 
per millisecond
 
 The JMX attribute `LastExchangeCompletedExchangeId`, of the `CamelContext`, 
route and processor MBeans, is now updated

Reply via email to