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;
