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

davsclaus pushed a commit to branch feat/camel-tui
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 93ce4dd103deea290b7fa53fa7556d10a7ad2834
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon May 18 14:44:55 2026 +0200

    TUI: add operationId from OpenAPI contract to RestRegistry and HTTP detail 
panel
    
    - RestRegistry.RestService: add getOperationId() (@Nullable, contract-first 
only)
    - RestRegistry.addRestService(): add operationId parameter
    - DefaultRestRegistry: store and expose operationId in RestServiceEntry
    - RestOpenApiProcessor: populate operationId from Operation.getOperationId()
    - RestEndpoint (REST DSL): passes null for operationId (code-first has none)
    - RestDevConsole: emit operationId in JSON and text output when present
    - CamelMonitor HTTP detail panel: show Operation field when operationId is 
set
    - Also commit SyntheticBacklogTracer.java (was untracked, needed for build)
    
    Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
---
 .../rest/openapi/RestOpenApiProcessor.java         |  3 +-
 .../camel/component/rest/DefaultRestRegistry.java  | 16 +++--
 .../apache/camel/component/rest/RestEndpoint.java  |  2 +-
 .../component/rest/DefaultRestRegistryTest.java    | 22 +++----
 .../component/rest/RestRegistryStatefulTest.java   |  6 +-
 .../java/org/apache/camel/spi/RestRegistry.java    | 11 +++-
 .../apache/camel/spi/SyntheticBacklogTracer.java   | 70 ++++++++++++++++++++++
 .../apache/camel/impl/console/RestDevConsole.java  | 22 ++++---
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  |  5 ++
 9 files changed, 124 insertions(+), 33 deletions(-)

diff --git 
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
 
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
index 9ee7eb9d11ec..c2680dda5eb6 100644
--- 
a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
+++ 
b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java
@@ -167,13 +167,14 @@ public class RestOpenApiProcessor extends 
AsyncProcessorSupport implements Camel
                 if (desc != null && desc.isBlank()) {
                     desc = null;
                 }
+                String operationId = o.getValue().getOperationId();
                 String routeId = null;
                 if (consumer instanceof RouteAware ra) {
                     routeId = ra.getRoute().getRouteId();
                 }
                 RestRegistry restRegistry = 
PluginHelper.getRestRegistry(camelContext);
                 restRegistry.addRestService(consumer, true, url, path, 
basePath, null, v, bc.getConsumes(),
-                        bc.getProduces(), bc.getType(), bc.getOutType(), 
routeId, desc);
+                        bc.getProduces(), bc.getType(), bc.getOutType(), 
routeId, operationId, desc);
 
                 try {
                     RestBindingAdvice binding = 
RestBindingAdviceFactory.build(camelContext, bc);
diff --git 
a/components/camel-rest/src/main/java/org/apache/camel/component/rest/DefaultRestRegistry.java
 
b/components/camel-rest/src/main/java/org/apache/camel/component/rest/DefaultRestRegistry.java
index 9a33bb386ca0..550b53cdcf6a 100644
--- 
a/components/camel-rest/src/main/java/org/apache/camel/component/rest/DefaultRestRegistry.java
+++ 
b/components/camel-rest/src/main/java/org/apache/camel/component/rest/DefaultRestRegistry.java
@@ -50,10 +50,11 @@ public class DefaultRestRegistry extends ServiceSupport 
implements RestRegistry,
     public void addRestService(
             Consumer consumer, boolean contractFirst, String url, String 
baseUrl, String basePath, String uriTemplate,
             String method,
-            String consumes, String produces, String inType, String outType, 
String routeId, String description) {
+            String consumes, String produces, String inType, String outType, 
String routeId, String operationId,
+            String description) {
         RestServiceEntry entry = new RestServiceEntry(
                 consumer, false, contractFirst, url, baseUrl, basePath, 
uriTemplate, method, consumes, produces, inType,
-                outType, routeId, description);
+                outType, routeId, operationId, description);
         List<RestService> list = registry.computeIfAbsent(consumer, c -> new 
ArrayList<>());
         list.add(entry);
     }
@@ -64,7 +65,7 @@ public class DefaultRestRegistry extends ServiceSupport 
implements RestRegistry,
             String produces, String description) {
         RestServiceEntry entry = new RestServiceEntry(
                 consumer, true, contractFirst, url, baseUrl, basePath, null, 
method, null, produces, null, null, null,
-                description);
+                null, description);
         List<RestService> list = specs.computeIfAbsent(consumer, c -> new 
ArrayList<>());
         list.add(entry);
     }
@@ -203,12 +204,13 @@ public class DefaultRestRegistry extends ServiceSupport 
implements RestRegistry,
         private final String inType;
         private final String outType;
         private final String routeId;
+        private final String operationId;
         private final String description;
 
         private RestServiceEntry(Consumer consumer, boolean specification, 
boolean contractFirst, String url, String baseUrl,
                                  String basePath,
                                  String uriTemplate, String method, String 
consumes, String produces,
-                                 String inType, String outType, String 
routeId, String description) {
+                                 String inType, String outType, String 
routeId, String operationId, String description) {
             this.consumer = consumer;
             this.specification = specification;
             this.contractFirst = contractFirst;
@@ -222,6 +224,7 @@ public class DefaultRestRegistry extends ServiceSupport 
implements RestRegistry,
             this.inType = inType;
             this.outType = outType;
             this.routeId = routeId;
+            this.operationId = operationId;
             this.description = description;
         }
 
@@ -304,6 +307,11 @@ public class DefaultRestRegistry extends ServiceSupport 
implements RestRegistry,
             return routeId;
         }
 
+        @Override
+        public String getOperationId() {
+            return operationId;
+        }
+
         @Override
         public String getDescription() {
             return description;
diff --git 
a/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestEndpoint.java
 
b/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestEndpoint.java
index 0a241412c69a..710f31b96d45 100644
--- 
a/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestEndpoint.java
+++ 
b/components/camel-rest/src/main/java/org/apache/camel/component/rest/RestEndpoint.java
@@ -489,7 +489,7 @@ public class RestEndpoint extends DefaultEndpoint {
         // add to rest registry, so we can keep track of them
         RestRegistry registry = 
PluginHelper.getRestRegistry(getCamelContext());
         registry.addRestService(consumer, false, url, baseUrl, getPath(), 
getUriTemplate(),
-                getMethod(), getConsumes(), getProduces(), getInType(), 
getOutType(), getRouteId(), getDescription());
+                getMethod(), getConsumes(), getProduces(), getInType(), 
getOutType(), getRouteId(), null, getDescription());
         return consumer;
     }
 
diff --git 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/DefaultRestRegistryTest.java
 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/DefaultRestRegistryTest.java
index 136aab89d2f4..260e9983b46d 100644
--- 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/DefaultRestRegistryTest.java
+++ 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/DefaultRestRegistryTest.java
@@ -62,7 +62,7 @@ class DefaultRestRegistryTest {
         registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
                 "http://localhost:8080";, "/api", "/users", "GET",
                 "application/json", "application/json", "User", "User",
-                "route1", "Get all users");
+                "route1", null, "Get all users");
 
         assertThat(registry.size()).isEqualTo(1);
     }
@@ -72,12 +72,12 @@ class DefaultRestRegistryTest {
         registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
                 "http://localhost:8080";, "/api", "/users", "GET",
                 "application/json", "application/json", null, null,
-                "route1", "Get all users");
+                "route1", null, "Get all users");
 
         registry.addRestService(consumer2, false, 
"http://localhost:8080/api/orders";,
                 "http://localhost:8080";, "/api", "/orders", "POST",
                 "application/json", "application/json", "Order", "Order",
-                "route2", "Create order");
+                "route2", null, "Create order");
 
         assertThat(registry.size()).isEqualTo(2);
     }
@@ -86,11 +86,11 @@ class DefaultRestRegistryTest {
     void testAddMultipleServicesToSameConsumer() {
         registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
                 "http://localhost:8080";, "/api", "/users", "GET",
-                null, null, null, null, "route1", "Get users");
+                null, null, null, null, "route1", null, "Get users");
 
         registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users/{id}";,
                 "http://localhost:8080";, "/api", "/users/{id}", "GET",
-                null, null, null, null, "route2", "Get user by id");
+                null, null, null, null, "route2", null, "Get user by id");
 
         assertThat(registry.size()).isEqualTo(2);
     }
@@ -99,7 +99,7 @@ class DefaultRestRegistryTest {
     void testRemoveRestService() {
         registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
                 "http://localhost:8080";, "/api", "/users", "GET",
-                null, null, null, null, "route1", "Get users");
+                null, null, null, null, "route1", null, "Get users");
 
         assertThat(registry.size()).isEqualTo(1);
 
@@ -112,7 +112,7 @@ class DefaultRestRegistryTest {
     void testRemoveNonExistentService() {
         registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
                 "http://localhost:8080";, "/api", "/users", "GET",
-                null, null, null, null, "route1", "Get users");
+                null, null, null, null, "route1", null, "Get users");
 
         registry.removeRestService(consumer2);
 
@@ -124,7 +124,7 @@ class DefaultRestRegistryTest {
         registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
                 "http://localhost:8080";, "/api", "/users", "GET",
                 "application/json", "application/json", "User", "UserResponse",
-                "route1", "Get all users");
+                "route1", null, "Get all users");
 
         List<RestRegistry.RestService> services = 
registry.listAllRestServices();
 
@@ -149,7 +149,7 @@ class DefaultRestRegistryTest {
     void testContractFirstService() {
         registry.addRestService(consumer1, true, 
"http://localhost:8080/api/users";,
                 "http://localhost:8080";, "/api", "/users", "GET",
-                null, null, null, null, "route1", "Get users");
+                null, null, null, null, "route1", null, "Get users");
 
         List<RestRegistry.RestService> services = 
registry.listAllRestServices();
         assertThat(services.get(0).isContractFirst()).isTrue();
@@ -170,7 +170,7 @@ class DefaultRestRegistryTest {
     void testServiceState() {
         registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
                 "http://localhost:8080";, "/api", "/users", "GET",
-                null, null, null, null, "route1", "Get users");
+                null, null, null, null, "route1", null, "Get users");
 
         List<RestRegistry.RestService> services = 
registry.listAllRestServices();
         // Non-stateful consumer returns Stopped state
@@ -187,7 +187,7 @@ class DefaultRestRegistryTest {
     void testStopClearsRegistry() throws Exception {
         registry.addRestService(consumer1, false, 
"http://localhost:8080/api/users";,
                 "http://localhost:8080";, "/api", "/users", "GET",
-                null, null, null, null, "route1", "Get users");
+                null, null, null, null, "route1", null, "Get users");
 
         assertThat(registry.size()).isEqualTo(1);
 
diff --git 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestRegistryStatefulTest.java
 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestRegistryStatefulTest.java
index 4a7628ba52fb..7cf4922dc710 100644
--- 
a/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestRegistryStatefulTest.java
+++ 
b/components/camel-rest/src/test/java/org/apache/camel/component/rest/RestRegistryStatefulTest.java
@@ -73,7 +73,7 @@ class RestRegistryStatefulTest {
         registry.addRestService(consumer, false, 
"http://localhost:8080/api/users";,
                 "http://localhost:8080";, "/api", "/users", "GET",
                 "application/json", "application/json", null, null,
-                "route1", "Get users");
+                "route1", null, "Get users");
 
         List<RestRegistry.RestService> services = 
registry.listAllRestServices();
         assertThat(services).hasSize(1);
@@ -101,7 +101,7 @@ class RestRegistryStatefulTest {
 
         registry.addRestService(consumer, false, 
"http://localhost:8080/api/orders";,
                 "http://localhost:8080";, "/api", "/orders", "POST",
-                null, null, null, null, "route2", "Create order");
+                null, null, null, null, "route2", null, "Create order");
 
         List<RestRegistry.RestService> services = 
registry.listAllRestServices();
         assertThat(services.get(0).getState()).isEqualTo("Started");
@@ -117,7 +117,7 @@ class RestRegistryStatefulTest {
 
         registry.addRestService(consumer, false, 
"http://localhost:8080/api/items";,
                 "http://localhost:8080";, "/api", "/items", "DELETE",
-                null, null, null, null, "route3", "Delete item");
+                null, null, null, null, "route3", null, "Delete item");
 
         List<RestRegistry.RestService> services = 
registry.listAllRestServices();
         assertThat(services.get(0).getState()).isEqualTo("Suspended");
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/RestRegistry.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/RestRegistry.java
index a812401b6e95..852d3610e615 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/RestRegistry.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/RestRegistry.java
@@ -61,6 +61,14 @@ public interface RestRegistry extends StaticService {
         @Nullable
         String getRouteId();
 
+        /**
+         * Gets the operationId from the OpenAPI contract (contract-first 
routes only)
+         *
+         * @since 4.21
+         */
+        @Nullable
+        String getOperationId();
+
         /**
          * Gets the absolute url to the REST service (baseUrl + uriTemplate)
          */
@@ -138,13 +146,14 @@ public interface RestRegistry extends StaticService {
      * @param inType        optional detail input binding to a FQN class name
      * @param outType       optional detail output binding to a FQN class name
      * @param routeId       the id of the route this rest service will be using
+     * @param operationId   optional operationId from the OpenAPI contract
      * @param description   optional description about the service
      */
     void addRestService(
             Consumer consumer, boolean contractFirst, String url, String 
baseUrl, String basePath,
             @Nullable String uriTemplate, String method,
             @Nullable String consumes, @Nullable String produces, @Nullable 
String inType, @Nullable String outType,
-            String routeId, @Nullable String description);
+            String routeId, @Nullable String operationId, @Nullable String 
description);
 
     /**
      * Removes the REST service from the registry
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/SyntheticBacklogTracer.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/SyntheticBacklogTracer.java
new file mode 100644
index 000000000000..9f422d63a3d9
--- /dev/null
+++ 
b/core/camel-api/src/main/java/org/apache/camel/spi/SyntheticBacklogTracer.java
@@ -0,0 +1,70 @@
+/*
+ * 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.spi;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.NamedNode;
+
+/**
+ * Extended {@link BacklogTracer} API for components that process exchanges 
inline and bypass the normal route pipeline
+ * (e.g. mock mode in the rest-openapi consumer). Such components cannot rely 
on the automatic tracing that
+ * {@code CamelInternalProcessor} applies to every node in the route graph, so 
they must emit synthetic first/last trace
+ * events manually to participate in message-history capture.
+ * <p>
+ * Callers should obtain a {@link BacklogTracer} from the context extension 
and check whether it also implements this
+ * interface before invoking the synthetic tracing methods:
+ *
+ * <pre>{@code
+ * BacklogTracer bt = 
camelContext.getCamelContextExtension().getContextPlugin(BacklogTracer.class);
+ * if (bt instanceof SyntheticBacklogTracer st && (st.isEnabled() || 
st.isStandby())) {
+ *     st.traceFirstNode(node, exchange);
+ *     try {
+ *         // ... inline processing ...
+ *     } finally {
+ *         st.traceLastNode(node, exchange);
+ *     }
+ * }
+ * }</pre>
+ *
+ * @since 4.21
+ */
+public interface SyntheticBacklogTracer extends BacklogTracer {
+
+    /**
+     * Emits a synthetic <em>first</em> trace event ({@code first=true, 
last=false}) for the given node and exchange.
+     * <p>
+     * Call this before the inline processing begins. It pairs with {@link 
#traceLastNode} to bracket the operation,
+     * mirroring what {@code BacklogTracerRouteAdvice} does automatically for 
normal route nodes.
+     *
+     * @param node     the synthetic node representing the inline operation
+     * @param exchange the current exchange
+     * @since          4.21
+     */
+    void traceFirstNode(NamedNode node, Exchange exchange);
+
+    /**
+     * Emits a synthetic <em>last</em> trace event ({@code first=false, 
last=true}) for the given node and exchange.
+     * <p>
+     * Call this after the inline processing completes (typically in a {@code 
finally} block). The {@code last=true}
+     * flag triggers message-history completion in the tracer, making the 
exchange visible in the history view.
+     *
+     * @param node     the synthetic node representing the inline operation
+     * @param exchange the current exchange
+     * @since          4.21
+     */
+    void traceLastNode(NamedNode node, Exchange exchange);
+}
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RestDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RestDevConsole.java
index d47760833905..73a80ea85919 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/RestDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/RestDevConsole.java
@@ -32,22 +32,13 @@ public class RestDevConsole extends AbstractDevConsole {
         super("camel", "rest", "Rest", "Rest DSL Registry information");
     }
 
-    private RestRegistry rr;
-
-    @Override
-    protected void doInit() throws Exception {
-        super.doInit();
-
-        // camel-rest is optional
-        if 
(getCamelContext().getCamelContextExtension().isContextPluginInUse(RestRegistry.class))
 {
-            rr = PluginHelper.getRestRegistry(getCamelContext());
-        }
-    }
-
     @Override
     protected String doCallText(Map<String, Object> options) {
         StringBuilder sb = new StringBuilder();
 
+        // camel-rest is optional; look up lazily so rest-openapi routes 
(which register
+        // after route warm-up via afterPropertiesConfigured) are visible on 
first call
+        RestRegistry rr = PluginHelper.getRestRegistry(getCamelContext());
         if (rr != null) {
             for (RestRegistry.RestService rs : rr.listAllRestServices()) {
                 if (!sb.isEmpty()) {
@@ -61,6 +52,9 @@ public class RestDevConsole extends AbstractDevConsole {
                 if (rs.getRouteId() != null) {
                     sb.append(String.format("%n    Route Id: %s", 
rs.getRouteId()));
                 }
+                if (rs.getOperationId() != null) {
+                    sb.append(String.format("%n    Operation Id: %s", 
rs.getOperationId()));
+                }
                 if (rs.getConsumes() != null) {
                     sb.append(String.format("%n    Consumes: %s", 
rs.getConsumes()));
                 }
@@ -87,6 +81,7 @@ public class RestDevConsole extends AbstractDevConsole {
     protected Map<String, Object> doCallJson(Map<String, Object> options) {
         JsonObject root = new JsonObject();
 
+        RestRegistry rr = PluginHelper.getRestRegistry(getCamelContext());
         if (rr != null) {
             JsonArray list = new JsonArray();
             root.put("rests", list);
@@ -101,6 +96,9 @@ public class RestDevConsole extends AbstractDevConsole {
                 if (rs.getRouteId() != null) {
                     jo.put("routeId", rs.getRouteId());
                 }
+                if (rs.getOperationId() != null) {
+                    jo.put("operationId", rs.getOperationId());
+                }
                 if (rs.getConsumes() != null) {
                     jo.put("consumes", rs.getConsumes());
                 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
index 984623a46d9d..f74897c9b834 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
@@ -3690,6 +3690,9 @@ public class CamelMonitor extends CamelCommand {
         if (ep.routeId != null) {
             addDetailLine(lines, "Route", ep.routeId);
         }
+        if (ep.operationId != null) {
+            addDetailLine(lines, "Operation", ep.operationId);
+        }
         if (ep.state != null) {
             addDetailLine(lines, "State", ep.state);
         }
@@ -5427,6 +5430,7 @@ public class CamelMonitor extends CamelCommand {
                     ep.contractFirst = 
Boolean.TRUE.equals(rj.get("contractFirst"));
                     ep.specification = 
Boolean.TRUE.equals(rj.get("specification"));
                     ep.routeId = rj.getString("routeId");
+                    ep.operationId = rj.getString("operationId");
                     ep.state = rj.getString("state");
                     ep.inType = rj.getString("inType");
                     ep.outType = rj.getString("outType");
@@ -5767,6 +5771,7 @@ public class CamelMonitor extends CamelCommand {
         boolean contractFirst;
         boolean specification; // true = OpenAPI/Swagger spec endpoint
         String routeId;
+        String operationId;
         String description;
         String inType;
         String outType;

Reply via email to