From 7de657c28e31d6b2c928fefd6bb9273bace8f472 Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Wed, 21 Oct 2026 08:37:02 +0200 Subject: [PATCH 1/6] CAMEL-25771: camel-core - a route checksum and revision, so tools can tell which routes changed between two runs or after a reload Each route has a checksum of the route as written. It is a short hash of the route's steps, options and expressions, with property placeholders as written. It leaves out the ids Camel generates, the source locations, and the parts that route configurations add to every route. The same route gives the same checksum in every run. A new SPI, ModelChecksumGenerator, computes it when the route is prepared, before the route configurations are merged in. It is implemented in camel-xml-io with the generated ModelWriter, so one implementation gives every application the same checksum. Without camel-xml-io the checksum is null. The revision counts, by route id, the times a route was added again with another checksum in this run. Where both show: - Route, NamedRoute and the managed route (Checksum, Revision); - the route console; the route-structure and route-dump consoles have the checksum; - camel get route and camel top route, in their JSON and with --checksum; - the reload summary, which says how many routes changed and marks each one (changed) or (new); - the descriptions of the MCP tools, which say how to use the checksum. Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: Claus Ibsen --- .../catalog/dev-consoles/route-dump.json | 4 + .../catalog/dev-consoles/route-structure.json | 4 + .../camel/catalog/dev-consoles/route.json | 8 + .../java/org/apache/camel/NamedRoute.java | 15 ++ .../src/main/java/org/apache/camel/Route.java | 27 +++ .../camel/spi/ModelChecksumGenerator.java | 53 +++++ .../impl/engine/AbstractCamelContext.java | 35 +++ .../camel/impl/engine/DefaultRoute.java | 18 ++ .../apache/camel/dev-console/route-dump.json | 4 + .../camel/dev-console/route-structure.json | 4 + .../org/apache/camel/dev-console/route.json | 8 + .../camel/impl/console/RouteDevConsole.java | 8 +- .../impl/console/RouteDumpDevConsole.java | 7 +- .../console/RouteStructureDevConsole.java | 7 +- .../console/RouteChecksumDevConsoleTest.java | 76 +++++++ .../apache/camel/model/RouteDefinition.java | 15 ++ .../camel/model/RouteDefinitionHelper.java | 24 +++ .../apache/camel/impl/RouteChecksumTest.java | 199 ++++++++++++++++++ .../RouteWatcherReloadChangedRoutesTest.java | 161 ++++++++++++++ .../management/mbean/ManagedRouteMBean.java | 6 + .../camel/management/mbean/ManagedRoute.java | 10 + .../management/ManagedRouteChecksumTest.java | 59 ++++++ .../support/RouteWatcherReloadStrategy.java | 26 ++- .../org/apache/camel/model-checksum-generator | 2 + .../camel/xml/LwModelChecksumGenerator.java | 62 ++++++ .../apache/camel/xml/LwModelToXMLDumper.java | 24 ++- .../ROOT/pages/camel-jbang-managing.adoc | 5 + .../jbang-commands/camel-jbang-get-route.adoc | 1 + .../jbang-commands/camel-jbang-top-route.adoc | 1 + .../modules/ROOT/pages/route-reload.adoc | 16 ++ .../modules/ROOT/pages/routes.adoc | 32 +++ .../camel-jbang-commands-metadata.json | 4 +- .../commands/process/CamelRouteStatus.java | 16 ++ .../core/commands/process/CamelRouteTop.java | 5 + .../process/CamelRouteStatusTest.java | 28 +++ .../jbang/core/commands/mcp/RuntimeTools.java | 8 +- 36 files changed, 967 insertions(+), 15 deletions(-) create mode 100644 core/camel-api/src/main/java/org/apache/camel/spi/ModelChecksumGenerator.java create mode 100644 core/camel-console/src/test/java/org/apache/camel/impl/console/RouteChecksumDevConsoleTest.java create mode 100644 core/camel-core/src/test/java/org/apache/camel/impl/RouteChecksumTest.java create mode 100644 core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadChangedRoutesTest.java create mode 100644 core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteChecksumTest.java create mode 100644 core/camel-xml-io/src/generated/resources/META-INF/services/org/apache/camel/model-checksum-generator create mode 100644 core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelChecksumGenerator.java diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-dump.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-dump.json index dc181764ef54d..558f375eb34db 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-dump.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-dump.json @@ -96,6 +96,10 @@ "type": "string", "description": "The source location (only present when known)" }, + "checksum": { + "type": "string", + "description": "The checksum of the route as written, the same in every run: the dump is of this version of the route (only present when known, as it needs camel-xml-io)" + }, "format": { "type": "string", "description": "The dump format, xml\/yaml\/java (only present when the dump succeeded)" diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-structure.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-structure.json index 34190b878fc13..f0e228f960d1d 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-structure.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-structure.json @@ -100,6 +100,10 @@ "type": "string", "description": "The route description (only present when configured)" }, + "checksum": { + "type": "string", + "description": "The checksum of the route as written, the same in every run: the facts a tool keeps per line hold for this version of the route (only present when known, as it needs camel-xml-io)" + }, "code": { "type": "array", "items": { diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route.json index 20556d0d9c735..abef3939b9e18 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route.json @@ -133,6 +133,14 @@ "type": "string", "description": "The source that declares the templated route the route was created from (only present when known)" }, + "checksum": { + "type": "string", + "description": "The checksum of the route as written, the same in every run: compare it with an earlier run to tell whether the route changed (only present when known, as it needs camel-xml-io)" + }, + "revision": { + "type": "integer", + "description": "The revision of the route in this run: 1 when it is added, one more each time it is added again changed, such as by a reload (only present when the checksum is known)" + }, "state": { "type": "string", "description": "The route state" diff --git a/core/camel-api/src/main/java/org/apache/camel/NamedRoute.java b/core/camel-api/src/main/java/org/apache/camel/NamedRoute.java index b2ec7e1058fc8..1363186f910a9 100644 --- a/core/camel-api/src/main/java/org/apache/camel/NamedRoute.java +++ b/core/camel-api/src/main/java/org/apache/camel/NamedRoute.java @@ -100,4 +100,19 @@ default Resource getTemplatedRouteResource() { return null; } + /** + * Gets the checksum of the route as written: its steps, options and expressions, with property placeholders as + * written, without the ids Camel generates and where the route is in its source. The same route gives the same + * checksum in every run, so tools can tell which routes changed. It is computed when the route is prepared, before + * the parts of route configurations are added to it. + * + * @return the checksum, or null when it is not known (camel-xml-io is not on the classpath) + * @see org.apache.camel.spi.ModelChecksumGenerator + * @since 4.24 + */ + @Nullable + default String getChecksum() { + return null; + } + } diff --git a/core/camel-api/src/main/java/org/apache/camel/Route.java b/core/camel-api/src/main/java/org/apache/camel/Route.java index 7d1ca3dd1819d..745df07c73889 100644 --- a/core/camel-api/src/main/java/org/apache/camel/Route.java +++ b/core/camel-api/src/main/java/org/apache/camel/Route.java @@ -218,6 +218,33 @@ default Resource getTemplatedRouteResource() { return null; } + /** + * Gets the checksum of the route as written: its steps, options and expressions, with property placeholders as + * written, without the ids Camel generates and where the route is in its source. The same route gives the same + * checksum in every run, so tools can tell which routes changed between two runs. + * + * @return the checksum, or null when it is not known (camel-xml-io is not on the classpath) + * @see org.apache.camel.spi.ModelChecksumGenerator + * @since 4.24 + */ + @Nullable + default String getChecksum() { + return null; + } + + /** + * Gets the revision of the route in this run: 1 when the route is added, one more each time it is added again with + * another checksum, such as when a reload changed it. A reload that brings the route back as it was keeps the + * revision. The revision follows the route id, and starts over when the application restarts (tools compare + * checksums across runs). + * + * @return the revision, or 0 when it is not known (the checksum is not known) + * @since 4.24 + */ + default int getRevision() { + return 0; + } + /** * The source:line-number where the route input is located in the source code */ diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/ModelChecksumGenerator.java b/core/camel-api/src/main/java/org/apache/camel/spi/ModelChecksumGenerator.java new file mode 100644 index 0000000000000..91342aa765392 --- /dev/null +++ b/core/camel-api/src/main/java/org/apache/camel/spi/ModelChecksumGenerator.java @@ -0,0 +1,53 @@ +/* + * 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.CamelContext; +import org.apache.camel.NamedNode; +import org.jspecify.annotations.Nullable; + +/** + * Computes the checksum of a route as written, so tools can tell which routes changed between two runs or after a + * reload: the same route gives the same checksum in every run. + *

+ * The checksum is a hash of the whole route: its steps, their options and expressions, with property placeholders as + * written. It leaves out what does not change the route: the ids Camel generates, and where the route is in its source + * (moving the route in its file, or adding a route above it, keeps its checksum). + *

+ * The implementation is provided by {@code camel-xml-io}, which writes every option of the model, and is discovered via + * the service key {@link #FACTORY}. It is a single implementation, so a route gets the same checksum whichever modules + * an application has. + * + * @since 4.24 + */ +public interface ModelChecksumGenerator { + + /** + * Service factory key. + */ + String FACTORY = "model-checksum-generator"; + + /** + * Computes the checksum of the definition as written. + * + * @param context the CamelContext + * @param definition the definition, such as a route + * @return the checksum, a short hex digest, or null when it cannot be computed + */ + @Nullable + String generateChecksum(CamelContext context, NamedNode definition); +} diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java index 1641883748e7a..662d3545373a4 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java @@ -139,6 +139,7 @@ import org.apache.camel.spi.ManagementStrategy; import org.apache.camel.spi.MessageHistoryFactory; import org.apache.camel.spi.MessageSizeStrategy; +import org.apache.camel.spi.ModelChecksumGenerator; import org.apache.camel.spi.ModelJAXBContextFactory; import org.apache.camel.spi.ModelToJavaDumper; import org.apache.camel.spi.ModelToStructureDumper; @@ -251,6 +252,8 @@ public abstract class AbstractCamelContext extends BaseService private final StopWatch stopWatch = new StopWatch(false); private final ThreadLocal> componentsInCreation = ThreadLocal.withInitial(HashSet::new); private final Lock routesLock = new ReentrantLock(); + // the revisions of the routes by id, guarded by routesLock (CAMEL-25771) + private final Map routeRevisions = new HashMap<>(); private final Lock lock = new ReentrantLock(); private VetoCamelContextStartException vetoed; private String managementName; @@ -411,6 +414,7 @@ protected void initPlugins() { camelContextExtension.lazyAddContextPlugin(ModelToYAMLDumper.class, this::createModelToYAMLDumper); camelContextExtension.lazyAddContextPlugin(ModelToJavaDumper.class, this::createModelToJavaDumper); camelContextExtension.lazyAddContextPlugin(ModelToStructureDumper.class, this::createModelToStructureDumper); + camelContextExtension.lazyAddContextPlugin(ModelChecksumGenerator.class, this::createModelChecksumGenerator); camelContextExtension.lazyAddContextPlugin(DeferServiceFactory.class, this::createDeferServiceFactory); camelContextExtension.lazyAddContextPlugin(AnnotationBasedProcessorFactory.class, this::createAnnotationBasedProcessorFactory); @@ -4688,6 +4692,15 @@ protected void failOnStartup(Exception e) { protected abstract ModelToStructureDumper createModelToStructureDumper(); + /** + * The generator of the checksums of routes, or null without camel-xml-io on the classpath: then the routes + * have no checksum. + */ + protected ModelChecksumGenerator createModelChecksumGenerator() { + return ResolverHelper.resolveBootstrapService(getCamelContextReference(), ModelChecksumGenerator.FACTORY, + ModelChecksumGenerator.class).orElse(null); + } + protected abstract RestBindingJaxbDataFormatFactory createRestBindingJaxbDataFormatFactory(); protected abstract RestBindingJacksonXmlDataFormatFactory createRestBindingJacksonXmlDataFormatFactory(); @@ -4767,11 +4780,33 @@ public void addRoute(Route route) { routesLock.lock(); try { routes.add(route); + reviseRoute(route); } finally { routesLock.unlock(); } } + /** + * The revision of a route added: 1 the first time its id is added, one more when it is added again with another + * checksum (a reload that changed it), the same when it comes back as it was. It is kept by route id, so it + * outlives the route being removed and added again. + */ + private void reviseRoute(Route route) { + String checksum = route.getChecksum(); + if (route.getId() == null || checksum == null || !(route instanceof DefaultRoute defaultRoute)) { + return; + } + RouteRevision previous = routeRevisions.get(route.getId()); + int revision = previous == null ? 1 + : previous.checksum().equals(checksum) ? previous.revision() : previous.revision() + 1; + routeRevisions.put(route.getId(), new RouteRevision(checksum, revision)); + defaultRoute.setRevision(revision); + } + + /** The checksum a route id was last added with, and its revision. */ + private record RouteRevision(String checksum, int revision) { + } + public void removeRoute(Route route) { routesLock.lock(); try { diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoute.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoute.java index 5bf89e352077b..03047b959c83d 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoute.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoute.java @@ -82,6 +82,8 @@ public class DefaultRoute extends ServiceSupport implements Route { private final String sourceLocation; private final String sourceLocationShort; private final String routeTemplateId; + private final String checksum; + private volatile int revision; private final Resource templatedRouteResource; private final List eventDrivenProcessors = new ArrayList<>(); private final List interceptStrategies = new ArrayList<>(0); @@ -139,6 +141,7 @@ public DefaultRoute(CamelContext camelContext, NamedNode route, String routeId, NamedRoute namedRoute = route instanceof NamedRoute nr ? nr : null; this.routeTemplateId = namedRoute != null ? namedRoute.getRouteTemplateId() : null; this.templatedRouteResource = namedRoute != null ? namedRoute.getTemplatedRouteResource() : null; + this.checksum = namedRoute != null ? namedRoute.getChecksum() : null; } @Override @@ -233,6 +236,21 @@ public String getRouteTemplateId() { return routeTemplateId; } + @Override + public String getChecksum() { + return checksum; + } + + @Override + public int getRevision() { + return revision; + } + + /** Set by the CamelContext when the route is added: see {@link Route#getRevision()}. */ + void setRevision(int revision) { + this.revision = revision; + } + @Override public Resource getTemplatedRouteResource() { return templatedRouteResource; diff --git a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-dump.json b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-dump.json index dc181764ef54d..558f375eb34db 100644 --- a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-dump.json +++ b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-dump.json @@ -96,6 +96,10 @@ "type": "string", "description": "The source location (only present when known)" }, + "checksum": { + "type": "string", + "description": "The checksum of the route as written, the same in every run: the dump is of this version of the route (only present when known, as it needs camel-xml-io)" + }, "format": { "type": "string", "description": "The dump format, xml\/yaml\/java (only present when the dump succeeded)" diff --git a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-structure.json b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-structure.json index 34190b878fc13..f0e228f960d1d 100644 --- a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-structure.json +++ b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route-structure.json @@ -100,6 +100,10 @@ "type": "string", "description": "The route description (only present when configured)" }, + "checksum": { + "type": "string", + "description": "The checksum of the route as written, the same in every run: the facts a tool keeps per line hold for this version of the route (only present when known, as it needs camel-xml-io)" + }, "code": { "type": "array", "items": { diff --git a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route.json b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route.json index 20556d0d9c735..abef3939b9e18 100644 --- a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route.json +++ b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route.json @@ -133,6 +133,14 @@ "type": "string", "description": "The source that declares the templated route the route was created from (only present when known)" }, + "checksum": { + "type": "string", + "description": "The checksum of the route as written, the same in every run: compare it with an earlier run to tell whether the route changed (only present when known, as it needs camel-xml-io)" + }, + "revision": { + "type": "integer", + "description": "The revision of the route in this run: 1 when it is added, one more each time it is added again changed, such as by a reload (only present when the checksum is known)" + }, "state": { "type": "string", "description": "The route state" diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java index 5012dbf2654aa..aebdc51c97ecc 100644 --- a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java @@ -97,6 +97,8 @@ public record RouteEntry( @Metadata(description = "Whether the endpoint is remote") boolean remote, @Metadata(description = "The source location (only present when known)") String source, @Metadata(description = "The source that declares the templated route the route was created from (only present when known)") String templatedRouteSource, + @Metadata(description = "The checksum of the route as written, the same in every run: compare it with an earlier run to tell whether the route changed (only present when known, as it needs camel-xml-io)") String checksum, + @Metadata(description = "The revision of the route in this run: 1 when it is added, one more each time it is added again changed, such as by a reload (only present when the checksum is known)") Integer revision, @Metadata(description = "The route state") String state, @Metadata(description = "Whether the route supports suspension") boolean supportsSuspension, @Metadata(description = "Route uptime, human readable text") String uptime, @@ -176,6 +178,9 @@ protected String doCallText(Map options) { if (mrb.getTemplatedRouteSource() != null) { sb.append(String.format("%n Templated Route Source: %s", mrb.getTemplatedRouteSource())); } + if (mrb.getChecksum() != null) { + sb.append(String.format("%n Checksum: %s (revision %d)", mrb.getChecksum(), mrb.getRevision())); + } sb.append(String.format("%n State: %s", mrb.getState())); Route r = getCamelContext().getRoute(mrb.getRouteId()); sb.append(String.format("%n Supports Suspension: %s", r != null && r.supportsSuspension())); @@ -321,7 +326,8 @@ protected Map doCallJson(Map options) { list.add(new RouteEntry( mrb.getRouteId(), mrb.getRouteGroup(), mrb.getNodePrefixId(), mrb.getDescription(), mrb.getNote(), mrb.isCreatedByKamelet(), mrb.isCreatedByRouteTemplate(), mrb.getRouteTemplateId(), mrb.getEndpointUri(), - mrb.isRemoteEndpoint(), mrb.getSourceLocation(), mrb.getTemplatedRouteSource(), mrb.getState(), + mrb.isRemoteEndpoint(), mrb.getSourceLocation(), mrb.getTemplatedRouteSource(), mrb.getChecksum(), + mrb.getRevision() > 0 ? mrb.getRevision() : null, mrb.getState(), r != null && r.supportsSuspension(), mrb.getUptime(), varied > 0 ? varied : null, mixed > 0 ? mixed : null, lastError, stats, procList)); diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java index 7e4d707d2b4ed..5ab34c280fa11 100644 --- a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDumpDevConsole.java @@ -53,6 +53,7 @@ public record RouteEntry( @Metadata(description = "The route ID") String routeId, @Metadata(description = "The route's endpoint URI") String from, @Metadata(description = "The source location (only present when known)") String source, + @Metadata(description = "The checksum of the route as written, the same in every run: the dump is of this version of the route (only present when known, as it needs camel-xml-io)") String checksum, @Metadata(description = "The dump format, xml/yaml/java (only present when the dump succeeded)") String format, @Metadata(description = "The dumped route source code (only present when the dump succeeded)") List code) { } @@ -105,6 +106,9 @@ protected String doCallText(Map options) { if (mrb.getSourceLocation() != null) { sb.append(String.format("%n Source: %s", mrb.getSourceLocation())); } + if (mrb.getChecksum() != null) { + sb.append(String.format("%n Checksum: %s", mrb.getChecksum())); + } if (dump != null && !dump.isEmpty()) { sb.append("\n\n"); for (String line : dump.split("\n")) { @@ -154,7 +158,8 @@ protected Map doCallJson(Map options) { // ignore } - list.add(new RouteEntry(mrb.getRouteId(), mrb.getEndpointUri(), mrb.getSourceLocation(), format, code)); + list.add(new RouteEntry( + mrb.getRouteId(), mrb.getEndpointUri(), mrb.getSourceLocation(), mrb.getChecksum(), format, code)); return null; }; doCall(options, task); diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java index 4ca790e6c1250..d2cd8200a6406 100644 --- a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java @@ -76,6 +76,7 @@ public record RouteEntry( @Metadata(description = "The source location (only present when known)") String source, @Metadata(description = "The source line number (only present when known)") Integer line, @Metadata(description = "The route description (only present when configured)") String description, + @Metadata(description = "The checksum of the route as written, the same in every run: the facts a tool keeps per line hold for this version of the route (only present when known, as it needs camel-xml-io)") String checksum, @Metadata(description = "The route's structure, one entry per processor (only present when the dump succeeded)") List code) { } @@ -125,6 +126,9 @@ protected String doCallText(Map options) { sb.append(String.format("%n Source: %s", extractSourceLocationNoLineNumber(def.getResource().getLocation()))); } + if (def.getChecksum() != null) { + sb.append(String.format("%n Checksum: %s", def.getChecksum())); + } sb.append("\n\n"); // the body types line up in a column after the code (a long line pushes only its own types out) int width = 0; @@ -186,7 +190,8 @@ protected Map doCallJson(Map options) { // ignore } - list.add(new RouteEntry(def.getRouteId(), def.getEndpointUrl(), source, line, def.getDescription(), code)); + list.add(new RouteEntry( + def.getRouteId(), def.getEndpointUrl(), source, line, def.getDescription(), def.getChecksum(), code)); return null; }; doCall(options, task); diff --git a/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteChecksumDevConsoleTest.java b/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteChecksumDevConsoleTest.java new file mode 100644 index 0000000000000..fed414e196bbd --- /dev/null +++ b/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteChecksumDevConsoleTest.java @@ -0,0 +1,76 @@ +/* + * 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.console; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.console.DevConsole; +import org.apache.camel.support.PluginHelper; +import org.apache.camel.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * CAMEL-25771: the route consoles have the checksum of each route as written, and the route console its revision, so a + * tool can tell which routes changed and which version of a route the structure or the dump is of. + */ +public class RouteChecksumDevConsoleTest extends ContextTestSupport { + + @Override + protected boolean useJmx() { + // the route and route-dump consoles list the routes from JMX + return true; + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start").routeId("myRoute").to("mock:result"); + } + }; + } + + @Test + public void testChecksumInTheRouteConsoles() { + String checksum = context.getRoute("myRoute").getChecksum(); + Assertions.assertNotNull(checksum); + + JsonObject route = firstRoute("route"); + Assertions.assertEquals(checksum, route.getString("checksum")); + Assertions.assertEquals(1, route.getInteger("revision")); + String text = (String) console("route").call(DevConsole.MediaType.TEXT); + Assertions.assertTrue(text.contains("Checksum: " + checksum + " (revision 1)"), text); + + Assertions.assertEquals(checksum, firstRoute("route-structure").getString("checksum")); + text = (String) console("route-structure").call(DevConsole.MediaType.TEXT); + Assertions.assertTrue(text.contains("Checksum: " + checksum), text); + + Assertions.assertEquals(checksum, firstRoute("route-dump").getString("checksum")); + } + + private JsonObject firstRoute(String id) { + JsonObject out = (JsonObject) console(id).call(DevConsole.MediaType.JSON); + return (JsonObject) ((JsonArray) out.getCollection("routes")).get(0); + } + + private DevConsole console(String id) { + return PluginHelper.getDevConsoleResolver(context).resolveDevConsole(id); + } +} diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java index 3e6d91a120722..f93bca2ec0c97 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java @@ -98,6 +98,7 @@ public class RouteDefinition extends OutputDefinition private Map templateDefaultParameters; private RouteTemplateContext routeTemplateContext; private String routeTemplateId; + private String checksum; private Resource resource; private Resource templatedRouteResource; private String precondition; @@ -909,6 +910,20 @@ public void setRouteTemplateId(String routeTemplateId) { this.routeTemplateId = routeTemplateId; } + /** + * The checksum of the route as written, computed when the route is prepared, before the parts of route + * configurations are added to it; null when it is not known (camel-xml-io is not on the classpath). + */ + @Override + public String getChecksum() { + return checksum; + } + + @XmlTransient + public void setChecksum(String checksum) { + this.checksum = checksum; + } + public Resource getResource() { return resource; } diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java index 658af1592e8da..d17c24cdbfc9a 100644 --- a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java +++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinitionHelper.java @@ -38,6 +38,7 @@ import org.apache.camel.RuntimeCamelException; import org.apache.camel.model.rest.RestDefinition; import org.apache.camel.model.rest.VerbDefinition; +import org.apache.camel.spi.ModelChecksumGenerator; import org.apache.camel.spi.NodeIdFactory; import org.apache.camel.support.CamelContextHelper; import org.apache.camel.support.EndpointHelper; @@ -421,6 +422,11 @@ private static void prepareRouteImp( // init the route inputs initRouteInput(); + // the checksum of the route as written, before the parts of route configurations are merged into it + if (route.getChecksum() == null) { + route.setChecksum(checksum(context, route)); + } + // abstracts is the cross cutting concerns List> abstracts = new ArrayList<>(); @@ -455,6 +461,24 @@ private static void prepareRouteImp( route.getOutputs().addAll(0, upper); } + /** + * The checksum of a route as written: the same route gives the same checksum in every run, so tools can tell which + * routes changed (see {@link ModelChecksumGenerator}). A tool that reads a route from its source without running it + * gets the checksum of the running route from this method too. + * + * @param context the camel context + * @param route the route, before it is prepared + * @return the checksum, or null when it cannot be computed (camel-xml-io is not on the classpath) + */ + public static String checksum(CamelContext context, NamedNode route) { + if (context == null || route == null) { + return null; + } + ModelChecksumGenerator generator + = context.getCamelContextExtension().getContextPlugin(ModelChecksumGenerator.class); + return generator != null ? generator.generateChecksum(context, route) : null; + } + /** * Sanity check the route, that it has input(s) and outputs. * diff --git a/core/camel-core/src/test/java/org/apache/camel/impl/RouteChecksumTest.java b/core/camel-core/src/test/java/org/apache/camel/impl/RouteChecksumTest.java new file mode 100644 index 0000000000000..97270f86d9547 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/impl/RouteChecksumTest.java @@ -0,0 +1,199 @@ +/* + * 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.Properties; +import java.util.function.Consumer; + +import org.apache.camel.CamelContext; +import org.apache.camel.Route; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.builder.RouteConfigurationBuilder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * CAMEL-25771: each route has a checksum of the route as written, the same in every run, and a revision that counts the + * times it came back changed in this run, so tools can tell which routes changed. + */ +public class RouteChecksumTest { + + private static RouteBuilder orders(String target) { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:orders").routeId("orders") + .split(body()).parallelProcessing() + .to("log:line") + .end() + .to(target); + } + }; + } + + private static String checksum(String routeId, RoutesBuilder... builders) throws Exception { + return checksum(routeId, null, builders); + } + + private static String checksum(String routeId, Consumer setup, RoutesBuilder... builders) + throws Exception { + try (CamelContext context = new DefaultCamelContext()) { + if (setup != null) { + setup.accept(context); + } + for (RoutesBuilder builder : builders) { + context.addRoutes(builder); + } + context.start(); + Route route = context.getRoute(routeId); + assertNotNull(route, routeId); + return route.getChecksum(); + } + } + + @Test + public void testSameRouteSameChecksumInEveryRun() throws Exception { + String first = checksum("orders", orders("mock:out")); + String second = checksum("orders", orders("mock:out")); + + assertNotNull(first); + assertTrue(first.matches("[0-9a-f]{12}"), first); + assertEquals(first, second); + } + + @Test + public void testEditedRouteNewChecksum() throws Exception { + String before = checksum("orders", orders("mock:out")); + + assertNotEquals(before, checksum("orders", orders("mock:other"))); + // an option of an EIP is part of the route too + String sequential = checksum("orders", new RouteBuilder() { + @Override + public void configure() { + from("direct:orders").routeId("orders") + .split(body()) + .to("log:line") + .end() + .to("mock:out"); + } + }); + assertNotEquals(before, sequential); + } + + @Test + public void testGeneratedIdsAndOtherRoutesLeftOut() throws Exception { + String alone = checksum("orders", orders("mock:out")); + // another route first: the ids Camel generates for the steps of orders are not the same + String after = checksum("orders", new RouteBuilder() { + @Override + public void configure() { + from("direct:audit").to("log:audit").split(body()).to("log:a").end(); + } + }, orders("mock:out")); + + assertEquals(alone, after); + } + + @Test + public void testRouteConfigurationsLeftOut() throws Exception { + String plain = checksum("orders", orders("mock:out")); + // the onException of a route configuration is merged into every route, which are not changed by it + String configured = checksum("orders", new RouteConfigurationBuilder() { + @Override + public void configuration() { + routeConfiguration().onException(Exception.class).handled(true).to("log:failed"); + } + }, orders("mock:out")); + assertEquals(plain, configured); + + // one the route has itself is part of it + String own = checksum("orders", new RouteBuilder() { + @Override + public void configure() { + from("direct:orders").routeId("orders") + .onException(Exception.class).handled(true).to("log:failed").end() + .split(body()).parallelProcessing() + .to("log:line") + .end() + .to("mock:out"); + } + }); + assertNotEquals(plain, own); + } + + @Test + public void testPlaceholdersAsWritten() throws Exception { + String a = checksum("orders", context -> context.getPropertiesComponent().setInitialProperties(props("mock:a")), + orders("{{target}}")); + String b = checksum("orders", context -> context.getPropertiesComponent().setInitialProperties(props("mock:b")), + orders("{{target}}")); + + // another value is configuration, not another route + assertEquals(a, b); + } + + @Test + public void testSourceLocationsLeftOutAlsoWhileDebugging() throws Exception { + String plain = checksum("orders", orders("mock:out")); + String debugging = checksum("orders", context -> { + context.setSourceLocationEnabled(true); + context.setDebugging(true); + }, orders("mock:out")); + + assertEquals(plain, debugging); + } + + @Test + public void testRevisionCountsTheChanges() throws Exception { + try (CamelContext context = new DefaultCamelContext()) { + context.addRoutes(orders("mock:out")); + context.start(); + assertEquals(1, context.getRoute("orders").getRevision()); + + // removed and added again as it was: the same revision + replace(context, orders("mock:out")); + assertEquals(1, context.getRoute("orders").getRevision()); + + // changed: the next revision, which it keeps when it comes back the same + replace(context, orders("mock:other")); + assertEquals(2, context.getRoute("orders").getRevision()); + replace(context, orders("mock:other")); + assertEquals(2, context.getRoute("orders").getRevision()); + + // changed back: still a change in this run + replace(context, orders("mock:out")); + assertEquals(3, context.getRoute("orders").getRevision()); + } + } + + private static void replace(CamelContext context, RoutesBuilder builder) throws Exception { + context.getRouteController().stopRoute("orders"); + context.removeRoute("orders"); + context.addRoutes(builder); + } + + private static Properties props(String target) { + Properties answer = new Properties(); + answer.put("target", target); + return answer; + } +} diff --git a/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadChangedRoutesTest.java b/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadChangedRoutesTest.java new file mode 100644 index 0000000000000..6df62cd9b8307 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadChangedRoutesTest.java @@ -0,0 +1,161 @@ +/* + * 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.support; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.apache.camel.ContextTestSupport; +import org.apache.camel.Route; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.engine.DefaultRoutesLoader; +import org.apache.camel.spi.Resource; +import org.apache.camel.spi.RoutesLoader; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.config.Property; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * CAMEL-25771: the summary of a reload says how many routes the reload changed, and marks them: a route that came back + * with another checksum is changed, one that was not there before is new. + */ +public class RouteWatcherReloadChangedRoutesTest extends ContextTestSupport { + + @TempDir + Path dir; + + private final List lines = new CopyOnWriteArrayList<>(); + private Appender appender; + private NextRoutesLoader loader; + private RouteWatcherReloadStrategy strategy; + + private static RouteBuilder routes(String ordersTarget, boolean audit) { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:orders").routeId("orders").to(ordersTarget); + from("direct:stock").routeId("stock").to("mock:stock"); + if (audit) { + from("direct:audit").routeId("audit").to("mock:audit"); + } + } + }; + } + + @Override + protected RoutesBuilder createRouteBuilder() { + return routes("mock:out", false); + } + + @Override + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + loader = new NextRoutesLoader(); + loader.setCamelContext(context); + context.getCamelContextExtension().addContextPlugin(RoutesLoader.class, loader); + strategy = new RouteWatcherReloadStrategy(); + strategy.setCamelContext(context); + strategy.start(); + + appender = new AbstractAppender("reload-summary", null, null, true, Property.EMPTY_ARRAY) { + @Override + public void append(LogEvent event) { + if (RouteWatcherReloadStrategy.class.getName().equals(event.getLoggerName())) { + lines.add(event.getMessage().getFormattedMessage()); + } + } + }; + appender.start(); + LoggerContext logging = (LoggerContext) LogManager.getContext(false); + logging.getConfiguration().getRootLogger().addAppender(appender, Level.ALL, null); + logging.updateLoggers(); + } + + @AfterEach + public void stopCapture() { + strategy.stop(); + LoggerContext logging = (LoggerContext) LogManager.getContext(false); + logging.getConfiguration().getRootLogger().removeAppender(appender.getName()); + logging.updateLoggers(); + appender.stop(); + } + + @Test + public void theSummarySaysWhichRoutesTheReloadChanged() throws Exception { + // the file now sends the orders elsewhere and has a new route; the stock route is as it was + loader.next = routes("mock:other", true); + + strategy.onRouteReload(List.of(resource()), false); + + assertTrue(lines.contains("Routes reloaded summary (total:3 started:3 changed:2)"), lines.toString()); + assertTrue(line("orders").endsWith("(changed)"), line("orders")); + assertTrue(line("audit").endsWith("(new)"), line("audit")); + assertTrue(line("stock").endsWith(")") && !line("stock").contains("changed"), line("stock")); + assertEquals(2, context.getRoute("orders").getRevision()); + assertEquals(1, context.getRoute("stock").getRevision()); + + // saved again as it is: nothing changed + lines.clear(); + strategy.onRouteReload(List.of(resource()), false); + assertTrue(lines.contains("Routes reloaded summary (total:3 started:3 changed:0)"), lines.toString()); + assertEquals(2, context.getRoute("orders").getRevision()); + } + + private String line(String routeId) { + return lines.stream().filter(l -> l.contains(" " + routeId + " (")).findFirst().orElse(""); + } + + private Resource resource() throws Exception { + Path file = Files.writeString(dir.resolve("routes.yaml"), "- route: {}\n"); + return ResourceHelper.resolveMandatoryResource(context, "file:" + file); + } + + // loads the next version of the routes, whatever the file has + private static class NextRoutesLoader extends DefaultRoutesLoader { + private RoutesBuilder next; + + @Override + public Set updateRoutes(Collection resources) throws Exception { + for (Route route : List.copyOf(getCamelContext().getRoutes())) { + getCamelContext().getRouteController().stopRoute(route.getId()); + getCamelContext().removeRoute(route.getId()); + } + getCamelContext().addRoutes(next); + Set ids = new LinkedHashSet<>(); + getCamelContext().getRoutes().forEach(r -> ids.add(r.getId())); + return ids; + } + } +} diff --git a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java index d16ac9e8043ff..20a9f49777824 100644 --- a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java +++ b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java @@ -58,6 +58,12 @@ public interface ManagedRouteMBean extends ManagedPerformanceCounterMBean { @ManagedAttribute(description = "Route Source Location (Short)") String getSourceLocationShort(); + @ManagedAttribute(description = "The checksum of the route as written, the same in every run: compare it to tell whether the route changed (null when camel-xml-io is not on the classpath)") + String getChecksum(); + + @ManagedAttribute(description = "The revision of the route in this run: 1 when it is added, one more each time it is added again changed, such as by a reload (0 when the checksum is not known)") + int getRevision(); + @ManagedAttribute(description = "The number of steps that have been reached with more than one body type, across the messages (recorded while the message history is on)") int getVariedBodyTypes(); diff --git a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java index ccf087f26c874..cff094fc7858e 100644 --- a/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java +++ b/core/camel-management/src/main/java/org/apache/camel/management/mbean/ManagedRoute.java @@ -205,6 +205,16 @@ public String getSourceLocationShort() { return sourceLocationShort; } + @Override + public String getChecksum() { + return route.getChecksum(); + } + + @Override + public int getRevision() { + return route.getRevision(); + } + @Override public int getVariedBodyTypes() { BodyTypeRegistry registry = context.getCamelContextExtension().getContextPlugin(BodyTypeRegistry.class); diff --git a/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteChecksumTest.java b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteChecksumTest.java new file mode 100644 index 0000000000000..e0d1da041d9ad --- /dev/null +++ b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteChecksumTest.java @@ -0,0 +1,59 @@ +/* + * 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.management; + +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.apache.camel.builder.RouteBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import static org.apache.camel.management.DefaultManagementObjectNameStrategy.TYPE_ROUTE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * CAMEL-25771: a managed route has the checksum of the route as written, and its revision in this run. + */ +@DisabledOnOs(OS.AIX) +public class ManagedRouteChecksumTest extends ManagementTestSupport { + + @Test + public void testChecksumAndRevision() throws Exception { + MBeanServer mbeanServer = getMBeanServer(); + ObjectName on = getCamelObjectName(TYPE_ROUTE, "myRoute"); + + String checksum = (String) mbeanServer.getAttribute(on, "Checksum"); + assertNotNull(checksum); + assertTrue(checksum.matches("[0-9a-f]{12}"), checksum); + assertEquals(context.getRoute("myRoute").getChecksum(), checksum); + assertEquals(1, mbeanServer.getAttribute(on, "Revision")); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:start").routeId("myRoute").to("mock:result"); + } + }; + } +} diff --git a/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java b/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java index eb2027a783f49..1d97fec5da472 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/RouteWatcherReloadStrategy.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Deque; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -578,6 +579,10 @@ protected void onRouteReload(Collection resources, boolean removeEvery }); } + // the revision of each route before the reload, to tell the routes it changed (CAMEL-25771) + Map revisions = new HashMap<>(); + getCamelContext().getRoutes().forEach(r -> revisions.put(r.getId(), r.getRevision())); + try { // should all existing routes be stopped and removed first? if (removeAllRoutes) { @@ -666,6 +671,9 @@ protected void onRouteReload(Collection resources, boolean removeEvery List lines = new ArrayList<>(); int total = 0; int started = 0; + // the routes the reload changed, known when the routes have checksums (camel-xml-io) + int changed = 0; + boolean checksums = true; for (String id : ids) { total++; Route route = getCamelContext().getRoute(id); @@ -686,10 +694,24 @@ protected void onRouteReload(Collection resources, boolean removeEvery if (loc == null) { loc = ""; } - lines.add(String.format(" %s %s (%s) (source: %s)", status, id, uri, loc)); + String change = ""; + if (route.getRevision() == 0) { + checksums = false; + } else if (!revisions.containsKey(id)) { + change = " (new)"; + changed++; + } else if (revisions.get(id) != route.getRevision()) { + change = " (changed)"; + changed++; + } + lines.add(String.format(" %s %s (%s) (source: %s)%s", status, id, uri, loc, change)); } } - LOG.info("Routes reloaded summary (total:{} started:{})", total, started); + if (checksums) { + LOG.info("Routes reloaded summary (total:{} started:{} changed:{})", total, started, changed); + } else { + LOG.info("Routes reloaded summary (total:{} started:{})", total, started); + } // if we are default/verbose then log each route line if (getCamelContext().getStartupSummaryLevel() == StartupSummaryLevel.Default || getCamelContext().getStartupSummaryLevel() == StartupSummaryLevel.Verbose) { diff --git a/core/camel-xml-io/src/generated/resources/META-INF/services/org/apache/camel/model-checksum-generator b/core/camel-xml-io/src/generated/resources/META-INF/services/org/apache/camel/model-checksum-generator new file mode 100644 index 0000000000000..4f7b3a5910ddd --- /dev/null +++ b/core/camel-xml-io/src/generated/resources/META-INF/services/org/apache/camel/model-checksum-generator @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.xml.LwModelChecksumGenerator diff --git a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelChecksumGenerator.java b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelChecksumGenerator.java new file mode 100644 index 0000000000000..16b0d4aa9724b --- /dev/null +++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelChecksumGenerator.java @@ -0,0 +1,62 @@ +/* + * 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.xml; + +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.util.HexFormat; + +import org.apache.camel.CamelContext; +import org.apache.camel.NamedNode; +import org.apache.camel.spi.ModelChecksumGenerator; +import org.apache.camel.spi.annotations.JdkService; +import org.apache.camel.xml.out.ModelWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link ModelChecksumGenerator} based on the generated {@link ModelWriter}, which writes every option of the model: + * the SHA-256 of the definition as XML, with property placeholders as written, without the ids Camel generates and + * without source locations (also while debugging). The first 12 hex characters of it are the checksum. + */ +@JdkService(ModelChecksumGenerator.FACTORY) +public class LwModelChecksumGenerator implements ModelChecksumGenerator { + + private static final Logger LOG = LoggerFactory.getLogger(LwModelChecksumGenerator.class); + + /** The bytes of the digest the checksum has: 12 hex characters. */ + private static final int CHECKSUM_BYTES = 6; + + @Override + public String generateChecksum(CamelContext context, NamedNode definition) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + try (Writer out = new OutputStreamWriter( + new DigestOutputStream(OutputStream.nullOutputStream(), digest), StandardCharsets.UTF_8)) { + LwModelToXMLDumper.write(context, definition, out, false, false, false); + } + return HexFormat.of().formatHex(digest.digest(), 0, CHECKSUM_BYTES); + } catch (Exception e) { + LOG.debug("Cannot compute the checksum of {} due to {}", definition, e.getMessage(), e); + return null; + } + } +} diff --git a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java index 915e4cceb4a1d..04a7e00d79620 100644 --- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java +++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.io.StringWriter; +import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -75,6 +76,20 @@ public String dumpModelAsXml( CamelContext context, NamedNode definition, boolean resolvePlaceholders, boolean generatedIds, boolean sourceLocation) throws Exception { + StringWriter buffer = new StringWriter(); + // while debugging the source locations are written too, for the debugger to show where a step is + write(context, definition, buffer, resolvePlaceholders, generatedIds, sourceLocation || context.isDebugging()); + return buffer.toString(); + } + + /** + * Writes the definition as XML: the dump, and the text the checksum of a route is computed from + * ({@link LwModelChecksumGenerator}), which never has the source locations. + */ + static void write( + CamelContext context, NamedNode definition, Writer out, boolean resolvePlaceholders, boolean generatedIds, + boolean sourceLocation) + throws Exception { Properties properties = new Properties(); Map namespaces = new LinkedHashMap<>(); @@ -82,7 +97,7 @@ public String dumpModelAsXml( List restorers = new ArrayList<>(); Consumer extractor = route -> { extractNamespaces(route, namespaces); - if (sourceLocation || context.isDebugging()) { + if (sourceLocation) { extractSourceLocations(route, locations); } restorers.add(resolveEndpointDslUris(route)); @@ -94,8 +109,7 @@ public String dumpModelAsXml( } }; - StringWriter buffer = new StringWriter(); - ModelWriter writer = new ModelWriter(buffer, BaseWriter.DEFAULT_NAMESPACE) { + ModelWriter writer = new ModelWriter(out, BaseWriter.DEFAULT_NAMESPACE) { @Override protected void doWriteOptionalIdentifiedDefinitionAttributes(OptionalIdentifiedDefinition def) throws IOException { @@ -113,7 +127,7 @@ protected void doWriteOptionalIdentifiedDefinitionAttributes(OptionalIdentifiedD doWriteAttribute("note", def.getNote()); } // write location information - if (sourceLocation || context.isDebugging()) { + if (sourceLocation) { int line = (def instanceof RouteDefinition ? ((RouteDefinition) def).getInput() : def).getLineNumber(); if (line != -1) { writer.addAttribute("sourceLineNumber", Integer.toString(line)); @@ -212,8 +226,6 @@ String resolve(String value, Properties properties) { } finally { restorers.forEach(Runnable::run); } - - return buffer.toString(); } @Override diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc index 7c0310bfda1db..74d1b4cc13f47 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc @@ -154,6 +154,11 @@ exchanges, and a route such as `direct:audit` has the remote exchanges of the ro `camel get processor --remote` and `camel top route --remote`, and the JSON of these commands has the remote counters as `remoteTotal`, `remoteFailed` and `remoteInflight`. +Use `--checksum` to show the xref:routes.adoc#_route_checksum_and_revision[checksum and revision] of each route. The +checksum is the same in every run while the route is not changed, so comparing it with an earlier run tells which routes +changed; the revision counts the times a reload changed the route since the integration started. The JSON output +(`--json`) always has them, as `checksum` and `revision`. + TIP: Run `camel get --help` to see all available sub-commands. == Top status diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-route.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-route.adoc index 7e5f3770b2c4f..2e5da04f11f15 100644 --- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-route.adoc +++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-route.adoc @@ -19,6 +19,7 @@ camel get route [options] [cols="2,5,1,2",options="header"] |=== | Option | Description | Default | Type +| `--checksum` | Include the checksum and revision of each route: the checksum is the same in every run while the route is not changed, and the revision counts the times a reload changed it | | boolean | `--description` | Include description in the ID column (if available) | | boolean | `--duration` | How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed) | | long | `--error` | Shows detailed information for routes that has error status | | boolean diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-top-route.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-top-route.adoc index 889a4ec5d2b06..6dd64fa4440f4 100644 --- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-top-route.adoc +++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-top-route.adoc @@ -19,6 +19,7 @@ camel top route [options] [cols="2,5,1,2",options="header"] |=== | Option | Description | Default | Type +| `--checksum` | Include the checksum and revision of each route: the checksum is the same in every run while the route is not changed, and the revision counts the times a reload changed it | | boolean | `--description` | Include description in the ID column (if available) | | boolean | `--duration` | How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed) | | long | `--error` | Shows detailed information for routes that has error status | | boolean diff --git a/docs/user-manual/modules/ROOT/pages/route-reload.adoc b/docs/user-manual/modules/ROOT/pages/route-reload.adoc index 3ffcd96d53509..f1f394dc56ce4 100644 --- a/docs/user-manual/modules/ROOT/pages/route-reload.adoc +++ b/docs/user-manual/modules/ROOT/pages/route-reload.adoc @@ -71,6 +71,22 @@ This is necessary because Apache Camel must stop the existing routes from runnin And adding new routes is therefore possible as they would have a new unique route id specified. +=== Which routes changed + +The summary that is logged after a reload says how many of the reloaded routes changed, and marks each one: a route +that came back with another xref:routes.adoc#_route_checksum_and_revision[checksum] is `(changed)`, and a route that +was not there before is `(new)`. A route that is saved again as it was is not marked. + +[source,text] +---- +Routes reloaded summary (total:3 started:3 changed:2) + Started orders (direct://orders) (source: orders.yaml:3) (changed) + Started stock (direct://stock) (source: orders.yaml:9) + Started audit (direct://audit) (source: orders.yaml:14) (new) +---- + +The revision of a route counts the times a reload changed it in this run. + === Live reload of resource files In addition to reloading routes, when `routesReloadEnabled=true` Camel also disables the diff --git a/docs/user-manual/modules/ROOT/pages/routes.adoc b/docs/user-manual/modules/ROOT/pages/routes.adoc index eac3d85f8de7d..f850bf6c7ebbe 100644 --- a/docs/user-manual/modules/ROOT/pages/routes.adoc +++ b/docs/user-manual/modules/ROOT/pages/routes.adoc @@ -259,6 +259,38 @@ YAML:: ---- ==== +== Route Checksum and Revision + +Each route has a _checksum_ of the route as written, which tools such as the Camel CLI, an IDE or an AI agent use to +tell which routes changed between two runs of an application, or after a reload. The same route gives the same checksum +in every run. + +The checksum is a short hash (12 hex characters) of the whole route: its steps, their options and expressions, with +property placeholders as written. It leaves out what does not change the route: + +* the ids Camel generates (a route or step with an id of its own keeps it in the checksum); +* where the route is in its source: moving the route in its file, or adding a route above it, keeps its checksum; +* the parts that route configurations add to every route (their `onException`, `intercept` and `onCompletion`): a +change in a shared route configuration does not change every route. The `onException` of a route itself is part of it. + +Each route also has a _revision_ in the running application: it is 1 when the route is added, and one more each time +the route is added again with another checksum, such as when xref:route-reload.adoc[route reloading] changed it. A +reload that brings the route back as it was keeps its revision. The revision follows the route id, and starts over +when the application restarts: to compare two runs, compare the checksums. + +The checksum and revision are on the `Route` (`getChecksum()` and `getRevision()`), on the route in JMX (the +`Checksum` and `Revision` attributes), in the `route` developer console, and in the Camel CLI +(`camel get route --checksum`). The `route-structure` and `route-dump` developer consoles have the checksum of each +route, so the facts a tool keeps per line of a route know which version of the route they belong to. + +The checksum needs `camel-xml-io` on the classpath, which comes with `camel-core`, `camel-management` and the Camel +CLI connector. Without it the routes have no checksum, and no revision. + +NOTE: The checksum covers the route, not the code it calls: changing a Java lambda in `.process(...)`, or a bean the route +calls, does not change it. Neither does a property value the route uses, which is configuration, not another route. A +route without an id of its own gets an id such as `route3`, which can move to another route when routes are added to or +removed from its file: match such routes by their checksum. + == More Information Check the xref:dsl.adoc[list of supported languages] that you can use for writing Camel routes. diff --git a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json index 27d9042058ecd..42e987a3ac181 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json +++ b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json @@ -15,7 +15,7 @@ { "name": "eval", "fullName": "eval", "description": "Evaluate Camel expressions and scripts", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.EvalCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "expression", "fullName": "eval expression", "description": "Evaluates Camel expression", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.EvalExpressionCommand", "options": [ { "names": "--body", "description": "Message body (prefix with file: to refer to loading message body from file)", "javaType": "java.lang.String", "type": "string" }, { "names": "--camel-version", "description": "To run using a different Camel version than the default version.", "javaType": "java.lang.String", "type": "string" }, { "names": "--download", "description": "Whether to allow automatic downloading JAR dependencies (over the internet)", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--fresh", "description": "Make sure we use fresh (i.e. non-cached) resources", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--header", "description": "Message header (key=value)", "javaType": "java.util.List", "type": "array" }, { "names": "--isolated", "description": "Whether to run evaluation isolated in local process", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--language", "description": "Language to use", "defaultValue": "simple", "javaType": "java.lang.String", "type": "string" }, { "names": "--predicate", "description": "Whether to force evaluating as predicate", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--repo,--repos", "description": "Additional maven repositories for download on-demand (Use commas to separate multiple repositories)", "javaType": "java.lang.String", "type": "string" }, { "names": "--template", "description": "The template to use for evaluating (prefix with file: to refer to loading template from file)", "javaType": "java.lang.String", "type": "string", "required": true }, { "names": "--timeout", "description": "Timeout waiting for evaluation to be done, in millis or as a duration such as 30s", "defaultValue": "10000", "javaType": "long", "type": "integer" }, { "names": "--variable", "description": "Exchange variable (key=value)", "javaType": "java.util.List", "type": "array" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] } ] }, { "name": "explain", "fullName": "explain", "description": "Explain what a Camel route does using AI\/LLM", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Explain", "options": [ { "names": "--api-key", "description": "API key for authentication. Also reads ANTHROPIC_API_KEY, OPENAI_API_KEY, WATSONX_APIKEY, or LLM_API_KEY env vars", "javaType": "java.lang.String", "type": "string" }, { "names": "--api-type", "description": "API type: 'ollama', 'openai' (OpenAI-compatible), 'anthropic' (Anthropic\/Vertex AI), or 'watsonx' (IBM watsonx.ai)", "javaType": "LlmClient.ApiType", "type": "object" }, { "names": "--catalog-context", "description": "Include Camel Catalog descriptions in the prompt", "javaType": "boolean", "type": "boolean" }, { "names": "--format", "description": "Output format (text, markdown)", "defaultValue": "text", "javaType": "java.lang.String", "type": "string" }, { "names": "--model", "description": "Model to use", "defaultValue": "DEFAULT_MODEL", "javaType": "java.lang.String", "type": "string" }, { "names": "--show-prompt", "description": "Show the prompt sent to the LLM", "javaType": "boolean", "type": "boolean" }, { "names": "--stream", "description": "Stream the response as it's generated (shows progress)", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--system-prompt", "description": "Custom system prompt", "javaType": "java.lang.String", "type": "string" }, { "names": "--temperature", "description": "Temperature for response generation (0.0-2.0)", "defaultValue": "0.7", "javaType": "double", "type": "number" }, { "names": "--timeout", "description": "Timeout in seconds for LLM response", "defaultValue": "120", "javaType": "int", "type": "integer" }, { "names": "--url", "description": "LLM API endpoint URL. Auto-detected from 'camel infra' for Ollama if not specified. Also reads AZURE_OPENAI_ENDPOINT or WATSONX_URL env vars", "javaType": "java.lang.String", "type": "string" }, { "names": "--verbose,-v", "description": "Include detailed technical information", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "export", "fullName": "export", "description": "Export to other runtimes (Camel Main, Spring Boot, or Quarkus)", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Export", "options": [ { "names": "--build-property", "description": "Maven build properties, ex. --build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, { "names": "--camel-spring-boot-version", "description": "Camel version to use with Spring Boot", "javaType": "java.lang.String", "type": "string" }, { "names": "--camel-version", "description": "To export using a different Camel version than the default version.", "javaType": "java.lang.String", "type": "string" }, { "names": "--camel-wrapper", "description": "Include Camel Wrapper scripts in exported project for version pinning", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--clean-dir", "description": "If exporting to current directory (default) then all existing files are preserved. Enabling this option will force cleaning current directory including all sub dirs (use this with care)", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--console", "description": "Developer console on the local HTTP server (port 8080 by default): \/q\/dev with Camel Main, \/actuator\/camel with Spring Boot, and \/q\/camel\/dev-console with Quarkus. The exported project only has the console with the dev profile.", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--dep,--dependency", "description": "Add additional dependencies", "javaType": "java.util.List", "type": "array" }, { "names": "--dir,--directory", "description": "Directory where the project will be exported", "defaultValue": ".", "javaType": "java.lang.String", "type": "string" }, { "names": "--docker", "description": "Include Docker files in exported project", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--download", "description": "Whether to allow automatic downloading JAR dependencies (over the internet)", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--dry-run", "description": "Preview export without writing files", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--exclude", "description": "Exclude files by name or pattern", "javaType": "java.util.List", "type": "array" }, { "names": "--fresh", "description": "Make sure we use fresh (i.e. non-cached) resources", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--gav", "description": "The Maven group:artifact:version", "javaType": "java.lang.String", "type": "string" }, { "names": "--groovy-pre-compiled", "description": "Whether to include pre-compiled Groovy classes in the export (only supported with runtime=camel-main)", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--hawtio", "description": "Whether to include Hawtio web console (only available for exporting to Spring Boot or Quarkus)", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--hawtio-version", "description": "Version of the Hawtio web console", "defaultValue": "HawtioVersion.HAWTIO_VERSION", "javaType": "java.lang.String", "type": "string" }, { "names": "--ignore-loading-error", "description": "Whether to ignore route loading and compilation errors (use this with care!)", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--java-version,--java", "description": "Java version (21, 25)", "defaultValue": "21", "javaType": "java.lang.String", "type": "string" }, { "names": "--kamelets-version", "description": "Apache Camel Kamelets version (auto-detected from classpath if not set)", "javaType": "java.lang.String", "type": "string" }, { "names": "--lazy-bean", "description": "Whether to use lazy bean initialization (can help with complex classloading issues", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--local-kamelet-dir", "description": "Local directory for loading Kamelets (takes precedence)", "javaType": "java.lang.String", "type": "string" }, { "names": "--logging", "description": "Can be used to turn on logging to console (logs by default to file in \/.camel directory)", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--logging-level", "description": "Logging level (ERROR, WARN, INFO, DEBUG, TRACE)", "defaultValue": "info", "javaType": "java.lang.String", "type": "string" }, { "names": "--main-classname", "description": "The class name of the Camel Main application class", "defaultValue": "CamelApplication", "javaType": "java.lang.String", "type": "string" }, { "names": "--management-port", "description": "To use a dedicated port for HTTP management", "javaType": "int", "type": "integer" }, { "names": "--maven-apache-snapshot-enabled", "description": "Whether downloading JARs from ASF Maven Snapshot repository is enabled", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--maven-central-enabled", "description": "Whether downloading JARs from Maven Central repository is enabled", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--maven-settings", "description": "Optional location of Maven settings.xml file to configure servers, repositories, mirrors and proxies. If set to false, not even the default ~\/.m2\/settings.xml will be used.", "javaType": "java.lang.String", "type": "string" }, { "names": "--maven-settings-security", "description": "Optional location of Maven settings-security.xml file to decrypt settings.xml", "javaType": "java.lang.String", "type": "string" }, { "names": "--maven-wrapper", "description": "Include Maven Wrapper files in exported project", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--name", "description": "The integration name. Use this when the name should not get derived otherwise.", "javaType": "java.lang.String", "type": "string" }, { "names": "--observe", "description": "Enable observability services", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--open-api", "description": "Adds an OpenAPI spec from the given file (json or yaml file)", "javaType": "java.lang.String", "type": "string" }, { "names": "--package-name", "description": "For Java source files should they have the given package name. By default the package name is computed from the Maven GAV. Use false to turn off and not include package name in the Java source files.", "javaType": "java.lang.String", "type": "string" }, { "names": "--package-scan-jars", "description": "Whether to automatic package scan JARs for custom Spring or Quarkus beans making them available for Camel CLI", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--parent-pom", "description": "The Maven parent POM group:artifact:version", "javaType": "java.lang.String", "type": "string" }, { "names": "--port", "description": "Embeds a local HTTP server on this port", "javaType": "int", "type": "integer" }, { "names": "--profile", "description": "Profile to export (dev, test, prod).", "javaType": "java.lang.String", "type": "string" }, { "names": "--prop,--property", "description": "Camel application properties, ex. --property=prop1=foo", "javaType": "java.lang.String", "type": "string" }, { "names": "--quarkus-artifact-id", "description": "Deprecated. This value is not used anymore. It is kept only for backwards compatibility and will be removed in Camel 5.x. Camel commands may use either 'quarkus-bom' or 'quarkus-camel-bom' artifactIds depending on the context.", "defaultValue": "quarkus-bom", "javaType": "java.lang.String", "type": "string", "deprecated": true }, { "names": "--quarkus-ext-registry", "description": "The base URI of Quarkus Extension Registry. The default is {@value RuntimeType#QUARKUS_EXTENSION_REGISTRY_BASE_URL} unless camel.jbang.quarkus.platform.url system property is set (the \/client\/platforms suffix is removed if present).", "javaType": "java.lang.String", "type": "string" }, { "names": "--quarkus-group-id", "description": "groupId of Quarkus Platform BOM; honored only if --quarkus-version is set", "defaultValue": "io.quarkus.platform", "javaType": "java.lang.String", "type": "string" }, { "names": "--quarkus-version", "description": "version of Quarkus Platform BOM; the default value is looked up in Quarkus Extension Registry", "javaType": "java.lang.String", "type": "string" }, { "names": "--quiet", "description": "Will be quiet, only print when error occurs", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--repo,--repos", "description": "Additional maven repositories for download on-demand (Use commas to separate multiple repositories)", "javaType": "java.lang.String", "type": "string" }, { "names": "--resource-dir,--resource-dirs", "description": "Additional resource directories to include in src\/main\/resources (recursive, preserving structure)", "javaType": "java.util.List", "type": "array" }, { "names": "--runtime", "description": "Runtime (camel-main, spring-boot, quarkus)", "javaType": "org.apache.camel.dsl.jbang.core.common.RuntimeType", "type": "object" }, { "names": "--skip-plugins", "description": "Skip plugins during export", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--spring-boot-version", "description": "Spring Boot version", "defaultValue": "RuntimeType.SPRING_BOOT_VERSION", "javaType": "java.lang.String", "type": "string" }, { "names": "--verbose", "description": "Verbose output of startup activity (dependency resolution and downloading", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--yes,-y", "description": "Automatically answer yes to confirmation prompts (e.g. when using --clean-dir)", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, - { "name": "get", "fullName": "get", "description": "Get status of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "activity", "fullName": "get activity", "description": "Get recent completed exchange activity", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListActivity", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter activity by route ID", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter activity by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name, age, elapsed, or since", "defaultValue": "since", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "bean", "fullName": "get bean", "description": "List beans in a running Camel integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelBeanDump", "options": [ { "names": "--dsl", "description": "Include only beans from YAML or XML DSL", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--filter", "description": "Filter beans names (use all to include all beans)", "defaultValue": "all", "javaType": "java.lang.String", "type": "string" }, { "names": "--internal", "description": "Include internal Camel beans", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--nulls", "description": "Include null values", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--properties", "description": "Show bean properties", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--scope", "description": "Filter beans by scope: all, user (excludes Camel\/Spring\/Quarkus\/JDK), camel, spring, quarkus", "defaultValue": "all", "javaType": "java.lang.String", "type": "string" }, { "names": "--sort", "description": "Sort by name or type", "defaultValue": "name", "javaType": "java.lang.String", "type": "string" }, { "names": "--timeout", "description": "How long to wait for the integration to answer, in millis or as a duration such as 30s", "defaultValue": "10000", "javaType": "long", "type": "integer" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "blocked", "fullName": "get blocked", "description": "Get blocked messages of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListBlocked", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "circuit-breaker", "fullName": "get circuit-breaker", "description": "Get status of Circuit Breaker EIPs", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListCircuitBreaker", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "consumer", "fullName": "get consumer", "description": "Get status of Camel consumers", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListConsumer", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter consumers by URI", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter consumers by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--scheduled", "description": "Filter consumer to only show scheduled based consumers", "javaType": "boolean", "type": "boolean" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "context", "fullName": "get context", "description": "Get status of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelContextStatus", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--remote", "description": "Break down counters into remote\/total pairs", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "count", "fullName": "get count", "description": "Get total and failed exchanges", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelCount", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--fail", "description": "Get the failed exchanges from a running integration", "javaType": "boolean", "type": "boolean" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--total", "description": "Get the total exchanges from a running integration", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "datasource", "fullName": "get datasource", "description": "Get status of DataSource connection pools", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListDataSource", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "endpoint", "fullName": "get endpoint", "description": "Get usage of Camel endpoints", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListEndpoint", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter endpoints by URI", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-direction", "description": "Filter by direction (in or out)", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-total", "description": "Filter endpoints that must be higher than the given usage", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter endpoints by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name, age, total, or size", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--verbose", "description": "Show additional size statistics (min\/max body and headers)", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "error", "fullName": "get error", "description": "Get captured routing errors of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListError", "options": [ { "names": "--ago", "description": "Filter by time window, e.g. 60s, 5m, 1h", "javaType": "java.lang.String", "type": "string" }, { "names": "--detail", "description": "Show full details of each error entry", "javaType": "boolean", "type": "boolean" }, { "names": "--diagram", "description": "Display a route diagram with the error path highlighted", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--exception", "description": "Filter by exception type (substring match)", "javaType": "java.lang.String", "type": "string" }, { "names": "--handled", "description": "Filter by handled status (true or false)", "javaType": "java.lang.String", "type": "string" }, { "names": "--id", "description": "Filter by exchange ID", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--last", "description": "Show only the last (newest) error with full details", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Maximum number of entries to display", "javaType": "int", "type": "integer" }, { "names": "--logging-color", "description": "Use colored logging", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--route", "description": "Filter by route ID", "javaType": "java.lang.String", "type": "string" }, { "names": "--show", "description": "Comma-separated detail sections to show: body, headers, properties, variables, history, stackTrace, or 'all' for all sections", "javaType": "java.lang.String", "type": "string" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--theme", "description": "Diagram color theme (ascii, unicode, dark, light, transparent, or custom)", "defaultValue": "unicode", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "event", "fullName": "get event", "description": "Get latest events of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListEvent", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter event by event type: context, route, or exchange", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "groovy", "fullName": "get groovy", "description": "Groovy Sources used of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListGroovy", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid or name", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "group", "fullName": "get group", "description": "Get status of Camel route groups", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelRouteGroupStatus", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter groups by name", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-mean", "description": "Filter groups that must be slower than the given time (ms)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter groups by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--running", "description": "Only include running groups", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name, age or group", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "health", "fullName": "get health", "description": "Get health check status of running Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListHealth", "options": [ { "names": "--depth", "description": "Max depth of stack-trace", "defaultValue": "1", "javaType": "int", "type": "integer" }, { "names": "--down", "description": "Show only checks which are DOWN", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--level", "description": "Level of details: full, or default", "defaultValue": "default", "javaType": "java.lang.String", "type": "string" }, { "names": "--live", "description": "Show only liveness checks", "javaType": "boolean", "type": "boolean" }, { "names": "--ready", "description": "Show only readiness checks", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--trace", "description": "Include stack-traces in error messages", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "history", "fullName": "get history", "description": "History of latest completed exchange", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelHistoryAction", "options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names": "--depth", "description": "Depth of tracing. 0=Created Completed. 1=All events on 1st route, 2=All events on 1st 2nd depth, and so on. 9 = all events on every depth.", "defaultValue": "9", "javaType": "int", "type": "integer" }, { "names": "--diagram", "description": "Display a route diagram with the message path highlighted", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--it", "description": "Interactive mode for enhanced history information", "javaType": "boolean", "type": "boolean" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit-split", "description": "Limit Split to a maximum number of entries to be displayed", "javaType": "int", "type": "integer" }, { "names": "--logging-color", "description": "Use colored logging", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--mask", "description": "Whether to mask endpoint URIs to avoid printing sensitive information such as password or access keys", "javaType": "boolean", "type": "boolean" }, { "names": "--pretty", "description": "Pretty print message body when using JSon or XML format", "javaType": "boolean", "type": "boolean" }, { "names": "--show-body", "description": "Show message body in debug messages", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--show-exception", "description": "Show exception and stacktrace for failed messages", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--show-exchange-properties", "description": "Show exchange properties in debug messages", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--show-exchange-variables", "description": "Show exchange variables in debug messages", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--show-headers", "description": "Show message headers in debug messages", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--source", "description": "Prefer to display source filename\/code instead of IDs", "javaType": "boolean", "type": "boolean" }, { "names": "--theme", "description": "Diagram color theme (ascii, unicode, dark, light, transparent, or custom)", "defaultValue": "unicode", "javaType": "java.lang.String", "type": "string" }, { "names": "--timeout", "description": "How long to wait for the integration to answer, in millis or as a duration such as 30s", "defaultValue": "10000", "javaType": "long", "type": "integer" }, { "names": "--timestamp", "description": "Print timestamp.", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "inflight", "fullName": "get inflight", "description": "Get inflight messages of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListInflight", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "internal-task", "fullName": "get internal-task", "description": "List internal tasks of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListInternalTask", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "kafka", "fullName": "get kafka", "description": "List Kafka consumers of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListKafka", "options": [ { "names": "--committed", "description": "Show committed offset (slower due to sync call to Kafka brokers)", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "metric", "fullName": "get metric", "description": "Get metrics (micrometer) of running Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListMetric", "options": [ { "names": "--all", "description": "Whether to show all metrics (also unused with counter being 0)", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--custom", "description": "Only show custom metrics", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter metric by type, name or tags", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--tags", "description": "Show metric tags", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "platform-http", "fullName": "get platform-http", "description": "Get embedded HTTP services of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListPlatformHttp", "options": [ { "names": "--all", "description": "Include management endpoints", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "processor", "fullName": "get processor", "description": "Get status of Camel processors", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelProcessorStatus", "options": [ { "names": "--description", "description": "Include description in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter processors by id", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-mean", "description": "Filter processors that must be slower than the given time (ms)", "javaType": "long", "type": "integer" }, { "names": "--group", "description": "Filter processors by group", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter routes by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--note", "description": "Include note in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--remote", "description": "Break down counters into remote\/total pairs", "javaType": "boolean", "type": "boolean" }, { "names": "--running", "description": "Only include running processors", "javaType": "boolean", "type": "boolean" }, { "names": "--show-group", "description": "Include group column", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid or name", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--source", "description": "Prefer to display source filename\/code instead of IDs", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "producer", "fullName": "get producer", "description": "Get status of Camel producers", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListProducer", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter producers by URI", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter producers by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "properties", "fullName": "get properties", "description": "List configuration properties", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListProperties", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--internal", "description": "Whether to include internal configuration", "javaType": "boolean", "type": "boolean" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--mask", "description": "Whether to mask configuration values to avoid printing sensitive information such as password or access keys", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or key", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--startup", "description": "List only startup configuration", "javaType": "boolean", "type": "boolean" }, { "names": "--verbose", "description": "Whether to include more details", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "rest", "fullName": "get rest", "description": "Get REST services of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListRest", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--verbose", "description": "Show more details", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "route", "fullName": "get route", "description": "Get status of Camel routes", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelRouteStatus", "options": [ { "names": "--description", "description": "Include description in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--error", "description": "Shows detailed information for routes that has error status", "javaType": "boolean", "type": "boolean" }, { "names": "--filter", "description": "Filter routes by id, or url", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-mean", "description": "Filter routes that must be slower than the given time (ms)", "javaType": "long", "type": "integer" }, { "names": "--group", "description": "Filter routes by group", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter routes by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--note", "description": "Include note in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--remote", "description": "Break down counters into remote\/total pairs", "javaType": "boolean", "type": "boolean" }, { "names": "--running", "description": "Only include running routes", "javaType": "boolean", "type": "boolean" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--show-group", "description": "Include group column", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--source", "description": "Prefer to display source filename\/code instead of IDs", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "route-controller", "fullName": "get route-controller", "description": "List status of route controller", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.RouteControllerAction", "options": [ { "names": "--depth", "description": "Max depth of stack-trace", "defaultValue": "1", "javaType": "int", "type": "integer" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--header", "description": "Include controller configuration details", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by id, or state", "defaultValue": "id", "javaType": "java.lang.String", "type": "string" }, { "names": "--timeout", "description": "How long to wait for the integration to answer, in millis or as a duration such as 30s", "defaultValue": "10000", "javaType": "long", "type": "integer" }, { "names": "--trace", "description": "Include stack-traces in error messages", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "service", "fullName": "get service", "description": "Get services of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListService", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--metadata", "description": "Show service metadata (only available for some services)", "javaType": "boolean", "type": "boolean" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "source", "fullName": "get source", "description": "Display Camel route source code", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelSourceAction", "options": [ { "names": "--filter", "description": "Filter source by filename (multiple names can be separated by comma)", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort source by name", "defaultValue": "name", "javaType": "java.lang.String", "type": "string" }, { "names": "--timeout", "description": "How long to wait for the integration to answer, in millis or as a duration such as 30s", "defaultValue": "10000", "javaType": "long", "type": "integer" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "sql-trace", "fullName": "get sql-trace", "description": "Get SQL query trace data", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListSqlTrace", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "startup-recorder", "fullName": "get startup-recorder", "description": "Display startup recording", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelStartupRecorderAction", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by duration, or type", "javaType": "java.lang.String", "type": "string" }, { "names": "--timeout", "description": "How long to wait for the integration to answer, in millis or as a duration such as 30s", "defaultValue": "10000", "javaType": "long", "type": "integer" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "transformer", "fullName": "get transformer", "description": "Get list of data type transformers", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListTransformer", "options": [ { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "variable", "fullName": "get variable", "description": "List variables of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListVariable", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or key", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "vault", "fullName": "get vault", "description": "List secrets from security vaults", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListVault", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] } ] }, + { "name": "get", "fullName": "get", "description": "Get status of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "activity", "fullName": "get activity", "description": "Get recent completed exchange activity", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListActivity", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter activity by route ID", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter activity by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name, age, elapsed, or since", "defaultValue": "since", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "bean", "fullName": "get bean", "description": "List beans in a running Camel integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelBeanDump", "options": [ { "names": "--dsl", "description": "Include only beans from YAML or XML DSL", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--filter", "description": "Filter beans names (use all to include all beans)", "defaultValue": "all", "javaType": "java.lang.String", "type": "string" }, { "names": "--internal", "description": "Include internal Camel beans", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--nulls", "description": "Include null values", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--properties", "description": "Show bean properties", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--scope", "description": "Filter beans by scope: all, user (excludes Camel\/Spring\/Quarkus\/JDK), camel, spring, quarkus", "defaultValue": "all", "javaType": "java.lang.String", "type": "string" }, { "names": "--sort", "description": "Sort by name or type", "defaultValue": "name", "javaType": "java.lang.String", "type": "string" }, { "names": "--timeout", "description": "How long to wait for the integration to answer, in millis or as a duration such as 30s", "defaultValue": "10000", "javaType": "long", "type": "integer" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "blocked", "fullName": "get blocked", "description": "Get blocked messages of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListBlocked", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "circuit-breaker", "fullName": "get circuit-breaker", "description": "Get status of Circuit Breaker EIPs", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListCircuitBreaker", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "consumer", "fullName": "get consumer", "description": "Get status of Camel consumers", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListConsumer", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter consumers by URI", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter consumers by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--scheduled", "description": "Filter consumer to only show scheduled based consumers", "javaType": "boolean", "type": "boolean" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "context", "fullName": "get context", "description": "Get status of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelContextStatus", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--remote", "description": "Break down counters into remote\/total pairs", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "count", "fullName": "get count", "description": "Get total and failed exchanges", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelCount", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--fail", "description": "Get the failed exchanges from a running integration", "javaType": "boolean", "type": "boolean" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--total", "description": "Get the total exchanges from a running integration", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "datasource", "fullName": "get datasource", "description": "Get status of DataSource connection pools", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListDataSource", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "endpoint", "fullName": "get endpoint", "description": "Get usage of Camel endpoints", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListEndpoint", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter endpoints by URI", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-direction", "description": "Filter by direction (in or out)", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-total", "description": "Filter endpoints that must be higher than the given usage", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter endpoints by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name, age, total, or size", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--verbose", "description": "Show additional size statistics (min\/max body and headers)", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "error", "fullName": "get error", "description": "Get captured routing errors of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListError", "options": [ { "names": "--ago", "description": "Filter by time window, e.g. 60s, 5m, 1h", "javaType": "java.lang.String", "type": "string" }, { "names": "--detail", "description": "Show full details of each error entry", "javaType": "boolean", "type": "boolean" }, { "names": "--diagram", "description": "Display a route diagram with the error path highlighted", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--exception", "description": "Filter by exception type (substring match)", "javaType": "java.lang.String", "type": "string" }, { "names": "--handled", "description": "Filter by handled status (true or false)", "javaType": "java.lang.String", "type": "string" }, { "names": "--id", "description": "Filter by exchange ID", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--last", "description": "Show only the last (newest) error with full details", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Maximum number of entries to display", "javaType": "int", "type": "integer" }, { "names": "--logging-color", "description": "Use colored logging", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--route", "description": "Filter by route ID", "javaType": "java.lang.String", "type": "string" }, { "names": "--show", "description": "Comma-separated detail sections to show: body, headers, properties, variables, history, stackTrace, or 'all' for all sections", "javaType": "java.lang.String", "type": "string" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--theme", "description": "Diagram color theme (ascii, unicode, dark, light, transparent, or custom)", "defaultValue": "unicode", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "event", "fullName": "get event", "description": "Get latest events of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListEvent", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter event by event type: context, route, or exchange", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "groovy", "fullName": "get groovy", "description": "Groovy Sources used of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListGroovy", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid or name", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "group", "fullName": "get group", "description": "Get status of Camel route groups", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelRouteGroupStatus", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter groups by name", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-mean", "description": "Filter groups that must be slower than the given time (ms)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter groups by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--running", "description": "Only include running groups", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name, age or group", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "health", "fullName": "get health", "description": "Get health check status of running Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListHealth", "options": [ { "names": "--depth", "description": "Max depth of stack-trace", "defaultValue": "1", "javaType": "int", "type": "integer" }, { "names": "--down", "description": "Show only checks which are DOWN", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--level", "description": "Level of details: full, or default", "defaultValue": "default", "javaType": "java.lang.String", "type": "string" }, { "names": "--live", "description": "Show only liveness checks", "javaType": "boolean", "type": "boolean" }, { "names": "--ready", "description": "Show only readiness checks", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--trace", "description": "Include stack-traces in error messages", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "history", "fullName": "get history", "description": "History of latest completed exchange", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelHistoryAction", "options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names": "--depth", "description": "Depth of tracing. 0=Created Completed. 1=All events on 1st route, 2=All events on 1st 2nd depth, and so on. 9 = all events on every depth.", "defaultValue": "9", "javaType": "int", "type": "integer" }, { "names": "--diagram", "description": "Display a route diagram with the message path highlighted", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--it", "description": "Interactive mode for enhanced history information", "javaType": "boolean", "type": "boolean" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit-split", "description": "Limit Split to a maximum number of entries to be displayed", "javaType": "int", "type": "integer" }, { "names": "--logging-color", "description": "Use colored logging", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--mask", "description": "Whether to mask endpoint URIs to avoid printing sensitive information such as password or access keys", "javaType": "boolean", "type": "boolean" }, { "names": "--pretty", "description": "Pretty print message body when using JSon or XML format", "javaType": "boolean", "type": "boolean" }, { "names": "--show-body", "description": "Show message body in debug messages", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--show-exception", "description": "Show exception and stacktrace for failed messages", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--show-exchange-properties", "description": "Show exchange properties in debug messages", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--show-exchange-variables", "description": "Show exchange variables in debug messages", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--show-headers", "description": "Show message headers in debug messages", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--source", "description": "Prefer to display source filename\/code instead of IDs", "javaType": "boolean", "type": "boolean" }, { "names": "--theme", "description": "Diagram color theme (ascii, unicode, dark, light, transparent, or custom)", "defaultValue": "unicode", "javaType": "java.lang.String", "type": "string" }, { "names": "--timeout", "description": "How long to wait for the integration to answer, in millis or as a duration such as 30s", "defaultValue": "10000", "javaType": "long", "type": "integer" }, { "names": "--timestamp", "description": "Print timestamp.", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "inflight", "fullName": "get inflight", "description": "Get inflight messages of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListInflight", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "internal-task", "fullName": "get internal-task", "description": "List internal tasks of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListInternalTask", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "kafka", "fullName": "get kafka", "description": "List Kafka consumers of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListKafka", "options": [ { "names": "--committed", "description": "Show committed offset (slower due to sync call to Kafka brokers)", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "metric", "fullName": "get metric", "description": "Get metrics (micrometer) of running Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListMetric", "options": [ { "names": "--all", "description": "Whether to show all metrics (also unused with counter being 0)", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--custom", "description": "Only show custom metrics", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter metric by type, name or tags", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--tags", "description": "Show metric tags", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "platform-http", "fullName": "get platform-http", "description": "Get embedded HTTP services of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListPlatformHttp", "options": [ { "names": "--all", "description": "Include management endpoints", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "processor", "fullName": "get processor", "description": "Get status of Camel processors", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelProcessorStatus", "options": [ { "names": "--description", "description": "Include description in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter processors by id", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-mean", "description": "Filter processors that must be slower than the given time (ms)", "javaType": "long", "type": "integer" }, { "names": "--group", "description": "Filter processors by group", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter routes by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--note", "description": "Include note in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--remote", "description": "Break down counters into remote\/total pairs", "javaType": "boolean", "type": "boolean" }, { "names": "--running", "description": "Only include running processors", "javaType": "boolean", "type": "boolean" }, { "names": "--show-group", "description": "Include group column", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid or name", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--source", "description": "Prefer to display source filename\/code instead of IDs", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "producer", "fullName": "get producer", "description": "Get status of Camel producers", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListProducer", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter producers by URI", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter producers by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "properties", "fullName": "get properties", "description": "List configuration properties", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListProperties", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--internal", "description": "Whether to include internal configuration", "javaType": "boolean", "type": "boolean" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--mask", "description": "Whether to mask configuration values to avoid printing sensitive information such as password or access keys", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or key", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--startup", "description": "List only startup configuration", "javaType": "boolean", "type": "boolean" }, { "names": "--verbose", "description": "Whether to include more details", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "rest", "fullName": "get rest", "description": "Get REST services of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListRest", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--verbose", "description": "Show more details", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "route", "fullName": "get route", "description": "Get status of Camel routes", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelRouteStatus", "options": [ { "names": "--checksum", "description": "Include the checksum and revision of each route: the checksum is the same in every run while the route is not changed, and the revision counts the times a reload changed it", "javaType": "boolean", "type": "boolean" }, { "names": "--description", "description": "Include description in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--error", "description": "Shows detailed information for routes that has error status", "javaType": "boolean", "type": "boolean" }, { "names": "--filter", "description": "Filter routes by id, or url", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-mean", "description": "Filter routes that must be slower than the given time (ms)", "javaType": "long", "type": "integer" }, { "names": "--group", "description": "Filter routes by group", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter routes by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--note", "description": "Include note in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--remote", "description": "Break down counters into remote\/total pairs", "javaType": "boolean", "type": "boolean" }, { "names": "--running", "description": "Only include running routes", "javaType": "boolean", "type": "boolean" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--show-group", "description": "Include group column", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--source", "description": "Prefer to display source filename\/code instead of IDs", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "route-controller", "fullName": "get route-controller", "description": "List status of route controller", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.RouteControllerAction", "options": [ { "names": "--depth", "description": "Max depth of stack-trace", "defaultValue": "1", "javaType": "int", "type": "integer" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--header", "description": "Include controller configuration details", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by id, or state", "defaultValue": "id", "javaType": "java.lang.String", "type": "string" }, { "names": "--timeout", "description": "How long to wait for the integration to answer, in millis or as a duration such as 30s", "defaultValue": "10000", "javaType": "long", "type": "integer" }, { "names": "--trace", "description": "Include stack-traces in error messages", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "service", "fullName": "get service", "description": "Get services of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListService", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--metadata", "description": "Show service metadata (only available for some services)", "javaType": "boolean", "type": "boolean" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "source", "fullName": "get source", "description": "Display Camel route source code", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelSourceAction", "options": [ { "names": "--filter", "description": "Filter source by filename (multiple names can be separated by comma)", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort source by name", "defaultValue": "name", "javaType": "java.lang.String", "type": "string" }, { "names": "--timeout", "description": "How long to wait for the integration to answer, in millis or as a duration such as 30s", "defaultValue": "10000", "javaType": "long", "type": "integer" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "sql-trace", "fullName": "get sql-trace", "description": "Get SQL query trace data", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListSqlTrace", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "startup-recorder", "fullName": "get startup-recorder", "description": "Display startup recording", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelStartupRecorderAction", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by duration, or type", "javaType": "java.lang.String", "type": "string" }, { "names": "--timeout", "description": "How long to wait for the integration to answer, in millis or as a duration such as 30s", "defaultValue": "10000", "javaType": "long", "type": "integer" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "transformer", "fullName": "get transformer", "description": "Get list of data type transformers", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListTransformer", "options": [ { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "variable", "fullName": "get variable", "description": "List variables of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListVariable", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or key", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "vault", "fullName": "get vault", "description": "List secrets from security vaults", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.ListVault", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] } ] }, { "name": "harden", "fullName": "harden", "description": "Suggest security hardening for Camel routes using AI\/LLM", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Harden", "options": [ { "names": "--api-key", "description": "API key for authentication. Also reads OPENAI_API_KEY or LLM_API_KEY env vars", "javaType": "java.lang.String", "type": "string" }, { "names": "--api-type", "description": "API type: 'ollama' or 'openai' (OpenAI-compatible)", "defaultValue": "ollama", "javaType": "org.apache.camel.dsl.jbang.core.commands.ApiType", "type": "object" }, { "names": "--catalog-context", "description": "Include Camel Catalog descriptions in the prompt", "javaType": "boolean", "type": "boolean" }, { "names": "--format", "description": "Output format (text, markdown)", "defaultValue": "text", "javaType": "java.lang.String", "type": "string" }, { "names": "--model", "description": "Model to use", "defaultValue": "DEFAULT_MODEL", "javaType": "java.lang.String", "type": "string" }, { "names": "--show-prompt", "description": "Show the prompt sent to the LLM", "javaType": "boolean", "type": "boolean" }, { "names": "--stream", "description": "Stream the response as it's generated (shows progress)", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--system-prompt", "description": "Custom system prompt", "javaType": "java.lang.String", "type": "string" }, { "names": "--temperature", "description": "Temperature for response generation (0.0-2.0)", "defaultValue": "0.7", "javaType": "double", "type": "number" }, { "names": "--timeout", "description": "Timeout in seconds for LLM response", "defaultValue": "120", "javaType": "int", "type": "integer" }, { "names": "--url", "description": "LLM API endpoint URL. Auto-detected from 'camel infra' for Ollama if not specified.", "javaType": "java.lang.String", "type": "string" }, { "names": "--verbose,-v", "description": "Include detailed security recommendations with code examples", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "hawtio", "fullName": "hawtio", "description": "Launch Hawtio web console", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.Hawtio", "options": [ { "names": "--host", "description": "Hostname to bind the Hawtio web console to", "defaultValue": "127.0.0.1", "javaType": "java.lang.String", "type": "string" }, { "names": "--open-url,--openUrl", "description": "To automatic open Hawtio web console in the web browser (without an interactive terminal the default is false)", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--port", "description": "Port number to use for Hawtio web console (port 8888 by default)", "defaultValue": "8888", "javaType": "int", "type": "integer" }, { "names": "--version", "description": "Version of the Hawtio web console", "defaultValue": "HawtioVersion.HAWTIO_VERSION", "javaType": "java.lang.String", "type": "string" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "infra", "fullName": "infra", "description": "List and Run external services for testing and prototyping", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.infra.InfraCommand", "options": [ { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", "fullName": "infra get", "description": "Displays running service(s) information", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.infra.InfraGet", "options": [ { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "list", "fullName": "infra list", "description": "Displays available external services", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.infra.InfraList", "options": [ { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "log", "fullName": "infra log", "description": "Displays external service logs", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.infra.InfraLog", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--follow", "description": "Keep following and outputting new log lines (press enter to exit). Without an interactive terminal the default is false, unless --duration is given.", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--lines", "description": "The number of lines from the end of the log to use as starting offset", "defaultValue": "50", "javaType": "int", "type": "integer" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "ps", "fullName": "infra ps", "description": "Displays running services", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.infra.InfraPs", "options": [ { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "restart", "fullName": "infra restart", "description": "Restarts a running external service", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.infra.InfraRestart", "options": [ { "names": "--background", "description": "Run in the background", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--kill", "description": "To force killing the process (SIGKILL) when stopping", "javaType": "boolean", "type": "boolean" }, { "names": "--log", "description": "Log container output to console", "javaType": "boolean", "type": "boolean" }, { "names": "--port", "description": "Override the default port for the service", "javaType": "java.lang.Integer", "type": "integer" }, { "names": "--prop,--property", "description": "Service properties, ex. --property=ollama.model=qwen2.5:0.5b", "javaType": "java.util.List", "type": "array" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "run", "fullName": "infra run", "description": "Run an external service", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.infra.InfraRun", "options": [ { "names": "--background", "description": "Run in the background", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--log", "description": "Log container output to console", "javaType": "boolean", "type": "boolean" }, { "names": "--no-ui", "description": "Do not start companion UI containers", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--port", "description": "Override the default port for the service", "javaType": "java.lang.Integer", "type": "integer" }, { "names": "--prop,--property", "description": "Service properties, ex. --property=ollama.model=qwen2.5:0.5b", "javaType": "java.util.List", "type": "array" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "stop", "fullName": "infra stop", "description": "Shuts down running external services", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.infra.InfraStop", "options": [ { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--kill", "description": "To force killing the process (SIGKILL)", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] } ] }, @@ -33,7 +33,7 @@ { "name": "semantic", "fullName": "semantic", "description": "List semantic definitions and expert contracts", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelSemantic", "options": [ { "names": "--expert", "description": "Show the operations and parameter contract of this expert", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output a single JSON document", "javaType": "boolean", "type": "boolean" }, { "names": "--timeout", "description": "Timeout waiting for the integration, in millis or as a duration such as 30s", "defaultValue": "60000", "javaType": "long", "type": "integer" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "audit", "fullName": "semantic audit", "description": "Retrieve retained expert evaluations and related lifecycle records", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.SemanticAudit", "options": [ { "names": "--breadcrumb-id", "description": "Filter by Camel breadcrumb ID", "javaType": "java.lang.String", "type": "string" }, { "names": "--case-event-id", "description": "Retrieve a source-local case anchored at this event", "javaType": "java.lang.String", "type": "string" }, { "names": "--category", "description": "Filter by record category: evaluation or request", "javaType": "java.lang.String", "type": "string" }, { "names": "--cursor", "description": "Opaque cursor for an older page; retain the original filters", "javaType": "java.lang.String", "type": "string" }, { "names": "--definition", "description": "Filter by recorded definition name", "javaType": "java.lang.String", "type": "string" }, { "names": "--event-id", "description": "Retrieve one recorded event instead of a page", "javaType": "java.lang.String", "type": "string" }, { "names": "--event-ids", "description": "Export these event IDs; at most 1000", "javaType": "java.util.List", "type": "array" }, { "names": "--exchange-id", "description": "Filter by recorded exchange ID, when supported", "javaType": "java.lang.String", "type": "string" }, { "names": "--executing-route-id", "description": "Filter by executing route ID, when supported", "javaType": "java.lang.String", "type": "string" }, { "names": "--expert", "description": "Filter by expert bean reference", "javaType": "java.lang.String", "type": "string" }, { "names": "--include-input", "description": "Include untrusted captured input in an explicit case\/export", "javaType": "boolean", "type": "boolean" }, { "names": "--json", "description": "Output a single JSON document", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Maximum records per page (1-200)", "defaultValue": "50", "javaType": "int", "type": "integer" }, { "names": "--max-records", "description": "Case\/export retrieval budget (1-1000)", "defaultValue": "200", "javaType": "int", "type": "integer" }, { "names": "--operation", "description": "Filter by recorded expert operation", "javaType": "java.lang.String", "type": "string" }, { "names": "--output", "description": "Write the full JSON response to a new file, including requested captured image data", "javaType": "java.nio.file.Path", "type": "object" }, { "names": "--result-label", "description": "Exact Classification member; requires --result-type=CLASSIFICATION", "javaType": "java.lang.String", "type": "string" }, { "names": "--result-type", "description": "Captured result type: BOOLEAN, CHOICE, SCORE or CLASSIFICATION", "javaType": "java.lang.String", "type": "string" }, { "names": "--result-value", "description": "Exact Boolean, Choice or numeric Score value; requires --result-type", "javaType": "java.lang.String", "type": "string" }, { "names": "--route-id", "description": "Filter by entry route ID", "javaType": "java.lang.String", "type": "string" }, { "names": "--score-max", "description": "Inclusive numeric upper bound; requires --result-type=SCORE", "javaType": "java.lang.String", "type": "string" }, { "names": "--score-min", "description": "Inclusive numeric lower bound; requires --result-type=SCORE", "javaType": "java.lang.String", "type": "string" }, { "names": "--since", "description": "Earliest timestamp, inclusive, in ISO-8601 format with a UTC offset", "javaType": "java.lang.String", "type": "string" }, { "names": "--timeout", "description": "Timeout waiting for the integration, in millis or as a duration such as 30s", "defaultValue": "60000", "javaType": "long", "type": "integer" }, { "names": "--until", "description": "Latest timestamp, exclusive, in ISO-8601 format with a UTC offset", "javaType": "java.lang.String", "type": "string" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "eval", "fullName": "semantic eval", "description": "Evaluate a semantic definition or expert operation", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.SemanticEvaluate", "options": [ { "names": "--body", "description": "Sample body (text, file:path, image:path or json:value)", "javaType": "java.lang.String", "type": "string" }, { "names": "--evaluation", "description": "Named semantic definition to evaluate", "javaType": "java.lang.String", "type": "string" }, { "names": "--evaluation-timeout", "description": "Provider evaluation timeout in milliseconds (1-50000)", "defaultValue": "50000", "javaType": "long", "type": "integer" }, { "names": "--expert", "description": "Expert to call directly", "javaType": "java.lang.String", "type": "string" }, { "names": "--header", "description": "Sample header (key=value; use json: for a typed value)", "javaType": "java.util.List", "type": "array" }, { "names": "--input", "description": "Direct input (text, file:path, image:path or json:value)", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output a single JSON document", "javaType": "boolean", "type": "boolean" }, { "names": "--operation", "description": "Expert operation to call directly", "javaType": "java.lang.String", "type": "string" }, { "names": "--parameter", "description": "Expert parameter (key=value; use json: for a typed value)", "javaType": "java.util.List", "type": "array" }, { "names": "--timeout", "description": "Timeout waiting for the integration, in millis or as a duration such as 30s", "defaultValue": "60000", "javaType": "long", "type": "integer" }, { "names": "--variable", "description": "Sample exchange-local variable (key=value; use json: for a typed value)", "javaType": "java.util.List", "type": "array" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "get", "fullName": "semantic get", "description": "List semantic definitions and expert contracts", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.SemanticGet", "options": [ { "names": "--expert", "description": "Show the operations and parameter contract of this expert", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output a single JSON document", "javaType": "boolean", "type": "boolean" }, { "names": "--timeout", "description": "Timeout waiting for the integration, in millis or as a duration such as 30s", "defaultValue": "60000", "javaType": "long", "type": "integer" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "monitoring", "fullName": "semantic monitoring", "description": "Read current semantic measurements", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.SemanticMonitoring", "options": [ { "names": "--json", "description": "Output a single JSON document", "javaType": "boolean", "type": "boolean" }, { "names": "--source", "description": "Producer or configured metric source name", "javaType": "java.lang.String", "type": "string" }, { "names": "--timeout", "description": "Timeout waiting for the integration, in millis or as a duration such as 30s", "defaultValue": "60000", "javaType": "long", "type": "integer" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] } ] }, { "name": "shell", "fullName": "shell", "description": "Interactive Camel CLI shell.", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Shell", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "stop", "fullName": "stop", "description": "Shuts down running Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.StopProcess", "options": [ { "names": "--kill", "description": "To force killing the process (SIGKILL)", "javaType": "boolean", "type": "boolean" }, { "names": "--no-wait", "description": "Do not wait for the integration to stop", "javaType": "boolean", "type": "boolean" }, { "names": "--timeout", "description": "How long to wait for the integration to stop, in millis or as a duration such as 30s", "defaultValue": "60000", "javaType": "long", "type": "integer" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, - { "name": "top", "fullName": "top", "description": "Top status of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelTop", "options": [ { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "context", "fullName": "top context", "description": "Top status of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelContextTop", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name, mem, or age", "defaultValue": "mem", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "group", "fullName": "top group", "description": "Top performing route groups", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelRouteGroupTop", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter groups by name", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-mean", "description": "Filter groups that must be slower than the given time (ms)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter groups by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--running", "description": "Only include running groups", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name, age or group", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "processor", "fullName": "top processor", "description": "Top performing processors", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelProcessorTop", "options": [ { "names": "--description", "description": "Include description in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter processors by id", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-mean", "description": "Filter processors that must be slower than the given time (ms)", "javaType": "long", "type": "integer" }, { "names": "--group", "description": "Filter processors by group", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter routes by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--note", "description": "Include note in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--remote", "description": "Break down counters into remote\/total pairs", "javaType": "boolean", "type": "boolean" }, { "names": "--running", "description": "Only include running processors", "javaType": "boolean", "type": "boolean" }, { "names": "--show-group", "description": "Include group column", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid or name", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--source", "description": "Prefer to display source filename\/code instead of IDs", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "route", "fullName": "top route", "description": "Top performing routes", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelRouteTop", "options": [ { "names": "--description", "description": "Include description in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--error", "description": "Shows detailed information for routes that has error status", "javaType": "boolean", "type": "boolean" }, { "names": "--filter", "description": "Filter routes by id, or url", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-mean", "description": "Filter routes that must be slower than the given time (ms)", "javaType": "long", "type": "integer" }, { "names": "--group", "description": "Filter routes by group", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter routes by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--note", "description": "Include note in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--remote", "description": "Break down counters into remote\/total pairs", "javaType": "boolean", "type": "boolean" }, { "names": "--running", "description": "Only include running routes", "javaType": "boolean", "type": "boolean" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--show-group", "description": "Include group column", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--source", "description": "Prefer to display source filename\/code instead of IDs", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "source", "fullName": "top source", "description": "List top processors (source) in a running Camel integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelSourceTop", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter-mean", "description": "Filter processors that must be slower than the given time (ms)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter processors by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--timeout", "description": "How long to wait for the integration to answer, in millis or as a duration such as 30s", "defaultValue": "10000", "javaType": "long", "type": "integer" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] } ] }, + { "name": "top", "fullName": "top", "description": "Top status of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelTop", "options": [ { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "context", "fullName": "top context", "description": "Top status of Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelContextTop", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name, mem, or age", "defaultValue": "mem", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "group", "fullName": "top group", "description": "Top performing route groups", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelRouteGroupTop", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter groups by name", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-mean", "description": "Filter groups that must be slower than the given time (ms)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter groups by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--running", "description": "Only include running groups", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name, age or group", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "processor", "fullName": "top processor", "description": "Top performing processors", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelProcessorTop", "options": [ { "names": "--description", "description": "Include description in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter", "description": "Filter processors by id", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-mean", "description": "Filter processors that must be slower than the given time (ms)", "javaType": "long", "type": "integer" }, { "names": "--group", "description": "Filter processors by group", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter routes by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--note", "description": "Include note in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--remote", "description": "Break down counters into remote\/total pairs", "javaType": "boolean", "type": "boolean" }, { "names": "--running", "description": "Only include running processors", "javaType": "boolean", "type": "boolean" }, { "names": "--show-group", "description": "Include group column", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid or name", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--source", "description": "Prefer to display source filename\/code instead of IDs", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "route", "fullName": "top route", "description": "Top performing routes", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.process.CamelRouteTop", "options": [ { "names": "--checksum", "description": "Include the checksum and revision of each route: the checksum is the same in every run while the route is not changed, and the revision counts the times a reload changed it", "javaType": "boolean", "type": "boolean" }, { "names": "--description", "description": "Include description in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--error", "description": "Shows detailed information for routes that has error status", "javaType": "boolean", "type": "boolean" }, { "names": "--filter", "description": "Filter routes by id, or url", "javaType": "java.lang.String", "type": "string" }, { "names": "--filter-mean", "description": "Filter routes that must be slower than the given time (ms)", "javaType": "long", "type": "integer" }, { "names": "--group", "description": "Filter routes by group", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter routes by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--note", "description": "Include note in the ID column (if available)", "javaType": "boolean", "type": "boolean" }, { "names": "--remote", "description": "Break down counters into remote\/total pairs", "javaType": "boolean", "type": "boolean" }, { "names": "--running", "description": "Only include running routes", "javaType": "boolean", "type": "boolean" }, { "names": "--short-uri", "description": "List endpoint URI without query parameters (short)", "javaType": "boolean", "type": "boolean" }, { "names": "--show-group", "description": "Include group column", "javaType": "boolean", "type": "boolean" }, { "names": "--sort", "description": "Sort by pid, name or age", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--source", "description": "Prefer to display source filename\/code instead of IDs", "javaType": "boolean", "type": "boolean" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "--wide-uri", "description": "List endpoint URI in full details", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "source", "fullName": "top source", "description": "List top processors (source) in a running Camel integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelSourceTop", "options": [ { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--filter-mean", "description": "Filter processors that must be slower than the given time (ms)", "javaType": "long", "type": "integer" }, { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--limit", "description": "Filter processors by limiting to the given number of rows", "javaType": "int", "type": "integer" }, { "names": "--timeout", "description": "How long to wait for the integration to answer, in millis or as a duration such as 30s", "defaultValue": "10000", "javaType": "long", "type": "integer" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] } ] }, { "name": "trace", "fullName": "trace", "description": "Tail message traces from running Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelTraceAction", "options": [ { "names": "--action", "description": "Action to start, stop, clear, list status, or dump traces", "defaultValue": "status", "javaType": "java.lang.String", "type": "string" }, { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names": "--compact", "description": "Compact output (no empty line separating traced messages)", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--depth", "description": "Depth of tracing. 0=Created Completed. 1=All events on 1st route, 2=All events on 1st 2nd depth, and so on. 9 = all events on every depth.", "defaultValue": "9", "javaType": "int", "type": "integer" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--find", "description": "Find and highlight matching text (ignore case).", "javaType": "java.lang.String", "type": "string" }, { "names": "--follow", "description": "Keep following and outputting new traces (press enter to exit). Without an interactive terminal the default is false, unless --duration is given.", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--grep", "description": "Filter traces to only output trace matching text (ignore case).", "javaType": "java.lang.String", "type": "string" }, { "names": "--json", "description": "Output the status of tracing in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--latest", "description": "Only output traces from the latest (follow if necessary until complete and exit)", "javaType": "boolean", "type": "boolean" }, { "names": "--logging-color", "description": "Use colored logging", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--mask", "description": "Whether to mask endpoint URIs to avoid printing sensitive information such as password or access keys", "javaType": "boolean", "type": "boolean" }, { "names": "--prefix", "description": "Print prefix with running Camel integration name. auto=only prefix when running multiple integrations. true=always prefix. false=prefix off.", "defaultValue": "auto", "javaType": "java.lang.String", "type": "string" }, { "names": "--pretty", "description": "Pretty print message body when using JSon or XML format", "javaType": "boolean", "type": "boolean" }, { "names": "--show-body", "description": "Show message body in traced messages", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--show-exception", "description": "Show exception and stacktrace for failed messages", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--show-exchange-properties", "description": "Show exchange properties in traced messages", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--show-exchange-variables", "description": "Show exchange variables in traced messages", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--show-headers", "description": "Show message headers in traced messages", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--since", "description": "Return traces newer than a relative duration like 5s, 2m, or 1h. The value is in seconds if no unit specified.", "javaType": "java.lang.String", "type": "string" }, { "names": "--sort", "description": "Sort by pid, name or age for showing status of tracing", "defaultValue": "pid", "javaType": "java.lang.String", "type": "string" }, { "names": "--source", "description": "Prefer to display source filename\/code instead of IDs", "javaType": "boolean", "type": "boolean" }, { "names": "--tail", "description": "The number of traces from the end of the trace to show. Use -1 to read from the beginning. Use 0 to read only new lines. Defaults to showing all traces from beginning.", "defaultValue": "-1", "javaType": "int", "type": "integer" }, { "names": "--timeout", "description": "How long to wait for the integration to answer, in millis or as a duration such as 30s", "defaultValue": "10000", "javaType": "long", "type": "integer" }, { "names": "--timestamp", "description": "Print timestamp.", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "transform", "fullName": "transform", "description": "Transform message or Camel routes", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.TransformCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "dataweave", "fullName": "transform dataweave", "description": "Convert DataWeave scripts to DataSonnet format", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.TransformDataWeave", "options": [ { "names": "--expression,-e", "description": "Inline DataWeave expression to convert", "javaType": "java.lang.String", "type": "string" }, { "names": "--include-comments", "description": "Include conversion notes as comments in output", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--input,-i", "description": "Input .dwl file or directory containing .dwl files", "javaType": "java.lang.String", "type": "string" }, { "names": "--output,-o", "description": "Output .ds file or directory (defaults to stdout)", "javaType": "java.lang.String", "type": "string" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "message", "fullName": "transform message", "description": "Transform message from one format to another via an existing running Camel integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.TransformMessageAction", "options": [ { "names": "--body", "description": "Message body to send (prefix with file: to refer to loading message body from file)", "javaType": "java.lang.String", "type": "string", "required": true }, { "names": "--camel-version", "description": "To run using a different Camel version than the default version.", "javaType": "java.lang.String", "type": "string" }, { "names": "--component", "description": "The component to use for message transformation", "javaType": "java.lang.String", "type": "string" }, { "names": "--dataformat", "description": "The dataformat to use for message transformation", "javaType": "java.lang.String", "type": "string" }, { "names": "--download", "description": "Whether to allow automatic downloading JAR dependencies (over the internet)", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--duration", "description": "How long to keep watching or following, in millis or as a duration such as 30s (default: until Enter is pressed)", "javaType": "long", "type": "integer" }, { "names": "--fresh", "description": "Make sure we use fresh (i.e. non-cached) resources", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--header", "description": "Message header (key=value)", "javaType": "java.util.List", "type": "array" }, { "names": "--language", "description": "The language to use for message transformation", "javaType": "java.lang.String", "type": "string" }, { "names": "--logging-color", "description": "Use colored logging", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--option", "description": "Option for additional configuration of the used language, component or dataformat (key=value)", "javaType": "java.util.List", "type": "array" }, { "names": "--output", "description": "File to store output. If none provide then output is printed to console.", "javaType": "java.lang.String", "type": "string" }, { "names": "--pretty", "description": "Pretty print message body when using JSon or XML format", "javaType": "boolean", "type": "boolean" }, { "names": "--repo,--repos", "description": "Additional maven repositories for download on-demand (Use commas to separate multiple repositories)", "javaType": "java.lang.String", "type": "string" }, { "names": "--show-body", "description": "Show message body from the output message", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--show-exception", "description": "Show exception and stacktrace for failed transformation", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--show-exchange-properties", "description": "Show exchange properties from the output message", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--show-headers", "description": "Show message headers from the output message", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--source", "description": "Instead of using external template file then refer to an existing Camel route source with inlined Camel language expression in a route. (use :line-number or :id to refer to the exact location of the EIP to use)", "javaType": "java.lang.String", "type": "string" }, { "names": "--template", "description": "The template to use for message transformation (prefix with file: to refer to loading template from file)", "javaType": "java.lang.String", "type": "string" }, { "names": "--timeout", "description": "Timeout waiting for message to be transformed, in millis or as a duration such as 30s", "defaultValue": "20000", "javaType": "long", "type": "integer" }, { "names": "--watch", "description": "Execute periodically and showing output fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "route", "fullName": "transform route", "description": "Transform Camel routes to XML, YAML or Java format", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.TransformRoute", "options": [ { "names": "--compile", "description": "Compile and run Java routes to transform them. By default Java routes are read without compiling them, and compiled only when a route cannot be read that way", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--download", "description": "Whether to allow automatic downloading JAR dependencies (over the internet)", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--format", "description": "Output format (xml, yaml, java), if only yaml files are provided, the format defaults to xml and vice versa. Java is converted without running the routes, one route builder class per file", "javaType": "java.lang.String", "type": "string" }, { "names": "--fresh", "description": "Make sure we use fresh (i.e. non-cached) resources", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--ignore-loading-error", "description": "Whether to ignore route loading and compilation errors (use this with care!)", "javaType": "boolean", "type": "boolean" }, { "names": "--output", "description": "File or directory to store transformed files. If none provide then output is printed to console. Use clipboard as name to copy content into clipboard.", "javaType": "java.lang.String", "type": "string" }, { "names": "--repo,--repos", "description": "Additional maven repositories for download on-demand (Use commas to separate multiple repositories)", "javaType": "java.lang.String", "type": "string" }, { "names": "--resolve-placeholders", "description": "Whether to resolve property placeholders in the dumped output", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--uri-as-parameters", "description": "Whether to expand URIs into separated key\/value parameters (only in use for YAML format)", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] } ] }, { "name": "update", "fullName": "update", "description": "Update Camel project", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.update.UpdateCommand", "options": [ { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "list", "fullName": "update list", "description": "List available update versions for Camel and its runtime variants", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.update.UpdateList", "options": [ { "names": "--json", "description": "Output in JSON Format", "javaType": "boolean", "type": "boolean" }, { "names": "--repo,--repos", "description": "Additional maven repositories for download on-demand (Use commas to separate multiple repositories)", "javaType": "java.lang.String", "type": "string" }, { "names": "--use-cache", "description": "Use Maven cache", "javaType": "boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] }, { "name": "run", "fullName": "update run", "description": "Update Camel project", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.update.UpdateRun", "options": [ { "names": "--camelArtifact", "description": "The Maven artifact coordinates for the Camel upgrade recipes.", "defaultValue": "camel-upgrade-recipes", "javaType": "java.lang.String", "type": "string" }, { "names": "--camelSpringBootArtifact", "description": "The Maven artifact coordinates for the Camel Spring Boot upgrade recipes.", "defaultValue": "camel-spring-boot-upgrade-recipes", "javaType": "java.lang.String", "type": "string" }, { "names": "--debug", "description": "Enables debug logging if set to true.", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--download", "description": "Whether to allow automatic downloading JAR dependencies (over the internet)", "defaultValue": "true", "javaType": "boolean", "type": "boolean" }, { "names": "--dryRun", "description": "If set to true, performs a dry run of the update process without making any changes.", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--extraActiveRecipes", "description": "Comma separated list of recipes to be executed after the Camel one, make sure the artifact containing the recipes is added via extraRecipeArtifactCoordinates", "javaType": "java.util.List", "type": "array" }, { "names": "--extraRecipeArtifactCoordinates", "description": "Comma separated list of artifact coordinates containing extraActiveRecipes, ex.my.org:recipes:1.0.0", "javaType": "java.util.List", "type": "array" }, { "names": "--fresh", "description": "Make sure we use fresh (i.e. non-cached) resources", "defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": "--openRewriteVersion", "description": "The version of OpenRewrite to use during the update process.", "defaultValue": "6.0.4", "javaType": "java.lang.String", "type": "string" }, { "names": "--quarkus-ext-registry", "description": "The base URI of Quarkus Extension Registry. The default is {@value RuntimeType#QUARKUS_EXTENSION_REGISTRY_BASE_URL} unless camel.jbang.quarkus.platform.url system property is set (the \/client\/platforms suffix is removed if present).", "javaType": "java.lang.String", "type": "string" }, { "names": "--quarkusMavenPluginGroupId", "description": "The group ID of the Quarkus Maven plugin.", "defaultValue": "io.quarkus.platform", "javaType": "java.lang.String", "type": "string" }, { "names": "--quarkusMavenPluginVersion", "description": "The version of the Quarkus Maven plugin to use; the default value is looked up in Quarkus Extension Registry", "javaType": "java.lang.String", "type": "string" }, { "names": "--repo,--repos", "description": "Additional maven repositories for download on-demand (Use commas to separate multiple repositories)", "javaType": "java.lang.String", "type": "string" }, { "names": "--runtime", "description": "Runtime (camel-main, spring-boot, quarkus)", "defaultValue": "camel-main", "javaType": "org.apache.camel.dsl.jbang.core.common.RuntimeType", "type": "object" }, { "names": "--upgradeTimeout", "description": "Time to wait, in seconds, before shutting down the upgrade process", "defaultValue": "240", "javaType": "int", "type": "integer" }, { "names": "-h,--help", "description": "Display the help and sub-commands", "javaType": "boolean", "type": "boolean" } ] } ] }, diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatus.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatus.java index 76af869d1fbdb..22a41c3218190 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatus.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatus.java @@ -114,6 +114,10 @@ public class CamelRouteStatus extends ProcessWatchCommand { description = "Include group column") boolean showGroup; + @CommandLine.Option(names = { "--checksum" }, + description = "Include the checksum and revision of each route: the checksum is the same in every run while the route is not changed, and the revision counts the times a reload changed it") + boolean checksum; + public CamelRouteStatus(CamelJBangMain main) { super(main); } @@ -149,6 +153,8 @@ public Integer doProcessWatchCall() throws Exception { row.remote = bool; } row.source = o.getString("source"); + row.checksum = o.getString("checksum"); + row.revision = o.getIntegerOrDefault("revision", 0); row.state = o.getString("state"); row.age = o.getString("uptime"); row.uptime = row.age != null ? TimeUtils.toMilliSeconds(row.age) : 0; @@ -307,6 +313,11 @@ protected void printTable(List rows, boolean remoteVisible) { new Column().header("FROM").visible(wideUri).dataAlign(HorizontalAlign.LEFT) .maxWidth(fromWideW, OverflowBehaviour.NEWLINE) .with(r -> r.from), + new Column().header("CHECKSUM").visible(checksum).dataAlign(HorizontalAlign.LEFT) + .with(r -> r.checksum != null ? r.checksum : ""), + new Column().header("REV").visible(checksum).headerAlign(HorizontalAlign.CENTER) + .dataAlign(HorizontalAlign.RIGHT) + .with(r -> r.revision > 0 ? String.valueOf(r.revision) : ""), new Column().header("REMOTE").visible(remoteVisible).headerAlign(HorizontalAlign.CENTER) .dataAlign(HorizontalAlign.CENTER) .with(this::getRemote), @@ -337,6 +348,9 @@ protected JsonObject toJson(Row r) { jo.put("group", r.group); jo.put("from", r.from); jo.put("remote", r.remote); + // the same in every run while the route is not changed, and the times a reload changed it (when known) + jo.put("checksum", r.checksum); + jo.put("revision", r.revision > 0 ? r.revision : null); jo.put("status", getStatus(r)); jo.put("age", r.age); jo.put("uptime", r.uptime); @@ -558,6 +572,8 @@ static class Row { String from; boolean remote; String source; + String checksum; + int revision; String state; String age; String coverage; diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteTop.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteTop.java index 667ab4f19cbba..7d6f5ef9c4141 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteTop.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteTop.java @@ -63,6 +63,11 @@ protected void printTable(List rows, boolean remoteVisible) { .with(this::getId), new Column().header("FROM").dataAlign(HorizontalAlign.LEFT).maxWidth(fromW, OverflowBehaviour.ELLIPSIS_RIGHT) .with(this::getFrom), + new Column().header("CHECKSUM").visible(checksum).dataAlign(HorizontalAlign.LEFT) + .with(r -> r.checksum != null ? r.checksum : ""), + new Column().header("REV").visible(checksum).headerAlign(HorizontalAlign.CENTER) + .dataAlign(HorizontalAlign.RIGHT) + .with(r -> r.revision > 0 ? String.valueOf(r.revision) : ""), new Column().header("REMOTE").visible(remoteVisible).headerAlign(HorizontalAlign.CENTER) .dataAlign(HorizontalAlign.CENTER) .with(this::getRemote), diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java index 40baa36fd3b55..8d31ed805a84b 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/process/CamelRouteStatusTest.java @@ -91,6 +91,34 @@ void jsonHasTheNumbersOfTheRoutes() throws Exception { assertThat(route2.get("mean")).isNull(); } + @Test + void checksumShowsTheChecksumAndRevisionOfEachRoute() throws Exception { + JsonObject changed = route("route1", "timer:tick", "Started", stats(12, 1, 2, 25)); + changed.put("checksum", "0a1b2c3d4e5f"); + changed.put("revision", 2); + writeStatusFile(TEST_PID, integration("myApp", changed, + route("route2", "timer:tock", "Started", stats(0, 0, 0, -1)))); + + assertThat(call(command())).isZero(); + assertThat(printer.getOutput()).doesNotContain("CHECKSUM", "0a1b2c3d4e5f"); + + printer = new StringPrinter(); + assertThat(call(command("--checksum"))).isZero(); + String[] lines = printer.getOutput().split(System.lineSeparator()); + assertThat(lines[0]).contains("CHECKSUM", "REV"); + assertThat(lines[1]).contains("route1", "0a1b2c3d4e5f", " 2 "); + + // the JSON has them whether asked for or not; a route of an older Camel has none + printer = new StringPrinter(); + assertThat(call(command("--json"))).isZero(); + JsonArray routes = Jsoner.deserialize(printer.getOutput(), (JsonArray) null); + JsonObject first = routes.getMap(0); + JsonObject second = routes.getMap(1); + assertThat(first.getString("checksum")).isEqualTo("0a1b2c3d4e5f"); + assertThat(first.getInteger("revision")).isEqualTo(2); + assertThat(second.get("revision")).isNull(); + } + @Test void jsonWithoutRoutesIsAnEmptyArray() throws Exception { writeStatusFile(TEST_PID, integration("myApp")); diff --git a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java index 64f26f63cffca..55017b894a70b 100644 --- a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java +++ b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RuntimeTools.java @@ -146,7 +146,10 @@ public JsonObject camel_runtime_context( @Tool(annotations = @Tool.Annotations(readOnlyHint = true, destructiveHint = false, openWorldHint = false), description = "List Camel routes with their state, uptime, messages processed, last error, and throughput statistics." + " A route with mixedBodyKinds has steps reached with bodies of more than one kind, which need" - + " attention: camel_runtime_route_structure with varied=true shows them.") + + " attention: camel_runtime_route_structure with varied=true shows them. The checksum of a route" + + " is the same in every run while the route is not changed: compare it with an earlier run to" + + " tell whether a route changed, and match routes with generated ids by it; revision counts the" + + " times a reload changed the route in this run.") public JsonObject camel_runtime_routes( @ToolArg(description = NAME_OR_PID_DESC, required = false) String nameOrPid) { return delegateToRegistry("get_routes", nameOrPid, Map.of()); @@ -244,7 +247,8 @@ public JsonObject camel_runtime_route_dump( + " a processor has bodyTypes: the body types it has been reached with, across the messages, and" + " bodyKinds: null, payload, data or collection. More than one kind needs attention: a step that" + " writes fails for data, one that reads fields fails for a payload. The routes tool counts them" - + " per route (mixedBodyKinds).") + + " per route (mixedBodyKinds). The checksum of each route says which version of the route the" + + " facts of its lines are of.") public JsonObject camel_runtime_route_structure( @ToolArg(description = NAME_OR_PID_DESC, required = false) String nameOrPid, @ToolArg(description = "Route ID or pattern such as orders* to inspect (default * for all routes)", From dcc3567bb1325c9123aef41583be13f66431b2f8 Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Wed, 21 Oct 2026 08:45:26 +0200 Subject: [PATCH 2/6] CAMEL-25771: camel-jbang - camel cmd route-structure gives the checksum of the routes of a source file Routes read from a source file without running them have the checksum they have when they run. A tool can compare the file with a running integration (camel get route --checksum). - The JSON route dump has the checksum of each route. - The routes of a Java source read by the Java DSL parser get it when the parser read the whole source. - camel cmd route-structure shows it under the source of each route. Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: Claus Ibsen --- .../camel/impl/DefaultDumpRoutesStrategy.java | 5 ++ .../ROOT/pages/camel-jbang-diagnostics.adoc | 13 +++++ .../modules/ROOT/pages/routes.adoc | 4 +- .../core/commands/TransformJavaRoutes.java | 9 +++ .../action/CamelRouteStructureAction.java | 6 ++ .../action/RouteActionJavaSourceTest.java | 56 +++++++++++++++++++ 6 files changed, 92 insertions(+), 1 deletion(-) 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 ab7f14e0ebf23..2f023a1205a68 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 @@ -783,6 +783,11 @@ protected void doDumpStructureJSon( if (loc != null) { jo.put("source", loc); } + // the checksum of the route as written: a tool that read the routes from their source compares it with + // the running routes to tell which changed + if (def.getChecksum() != null) { + jo.put("checksum", def.getChecksum()); + } List code = dumpAsJSon(lines); jo.put("code", code); list.add(jo); diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-diagnostics.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang-diagnostics.adoc index 62747d7ff785c..65bb5d422055e 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-jbang-diagnostics.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-diagnostics.adoc @@ -184,6 +184,19 @@ Give a source file instead of a running integration to see the structure without by the Java DSL parser, without compiling it; use `--compile` to compile it instead. `--brief` shortens each node, and `--description` shows the description of each node instead of its code. +Each route has its xref:routes.adoc#_route_checksum_and_revision[checksum] under its source, from a running +integration and from a source file alike: a route read from its file has the same checksum as when it runs, until it is +changed. Compare it with `camel get route --checksum` to tell whether the routes that run are the ones in the file. A +Java source file read without compiling it has the checksum only when the parser read all of it (else use `--compile`). + +[source,text] +---- +Source: orders.camel.yaml +Checksum: aa4535cb1725 +-------------------------------------------------------------------------------- + 4: route[orders] +---- + ==== Body types When the integration runs with the message history on, such as with `camel run --dev`, each step also shows the body diff --git a/docs/user-manual/modules/ROOT/pages/routes.adoc b/docs/user-manual/modules/ROOT/pages/routes.adoc index f850bf6c7ebbe..757ef2c6d6a4e 100644 --- a/docs/user-manual/modules/ROOT/pages/routes.adoc +++ b/docs/user-manual/modules/ROOT/pages/routes.adoc @@ -281,7 +281,9 @@ when the application restarts: to compare two runs, compare the checksums. The checksum and revision are on the `Route` (`getChecksum()` and `getRevision()`), on the route in JMX (the `Checksum` and `Revision` attributes), in the `route` developer console, and in the Camel CLI (`camel get route --checksum`). The `route-structure` and `route-dump` developer consoles have the checksum of each -route, so the facts a tool keeps per line of a route know which version of the route they belong to. +route, so the facts a tool keeps per line of a route know which version of the route they belong to. The Camel CLI +gives the checksum of the routes of a source file without running it (`camel cmd route-structure orders.camel.yaml`): +it is the one the route has when it runs. The checksum needs `camel-xml-io` on the classpath, which comes with `camel-core`, `camel-management` and the Camel CLI connector. Without it the routes have no checksum, and no revision. diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformJavaRoutes.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformJavaRoutes.java index 331a9fc698c46..e88334d1b733d 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformJavaRoutes.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformJavaRoutes.java @@ -168,6 +168,15 @@ private static void dump(Read read, String format, String output, boolean uriAsP context.addRouteDefinitions(r.routes().getRoutes()); } if ("json".equals(format)) { + // the checksum of each route as written, which is the one the route has when it runs: only when the + // parser read the whole source, as a part it could not read is missing from the routes + if (read.unread().isEmpty()) { + for (RouteDefinition route : context.getRouteDefinitions()) { + if (route.getChecksum() == null) { + route.setChecksum(RouteDefinitionHelper.checksum(context, route)); + } + } + } // the ids a start assigns: the route structure looks each route up by its id, and shows the step ids // (a route id that is a property placeholder is kept as it is, as there are no properties) context.getPropertiesComponent().setIgnoreMissingProperty(true); diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java index eac486c2ed0c9..7a95ca448babb 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java @@ -163,6 +163,7 @@ public Integer doCall() throws Exception { Row row = new Row(); row.location = extractSourceName(o.getString("source")); row.routeId = o.getString("routeId"); + row.checksum = o.getString("checksum"); if (!rows.contains(row)) { List lines = o.getCollection("code"); if (lines != null) { @@ -267,6 +268,10 @@ protected void printSource(List rows) { printer().println(); if (!raw) { printer().printf("Source: %s%n", row.location); + if (row.checksum != null) { + // the same as the checksum of the running route while the route is not changed + printer().printf("Checksum: %s%n", row.checksum); + } printer().println("--------------------------------------------------------------------------------"); } // the body types each step was reached with, as the running app has seen them, line up in a column after @@ -369,6 +374,7 @@ public static String extractSourceName(String loc) { private static class Row { String location; String routeId; + String checksum; List code = new ArrayList<>(); @Override diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteActionJavaSourceTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteActionJavaSourceTest.java index 24de5f0b77b50..e9d693bc2f7ba 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteActionJavaSourceTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteActionJavaSourceTest.java @@ -20,12 +20,16 @@ import java.nio.file.Path; import java.util.List; +import org.apache.camel.CamelContext; +import org.apache.camel.builder.RouteBuilder; import org.apache.camel.diagram.RouteDiagramLayoutEngine.RouteInfo; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; import org.apache.camel.dsl.jbang.core.commands.UsesWorkingDirectory; import org.apache.camel.dsl.jbang.core.common.CliException; import org.apache.camel.dsl.jbang.core.common.CliExitCode; import org.apache.camel.dsl.jbang.core.common.StringPrinter; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.model.ModelCamelContext; import org.apache.camel.util.json.JsonArray; import org.apache.camel.util.json.JsonObject; import org.apache.camel.util.json.Jsoner; @@ -212,6 +216,58 @@ void structureReadsJavaRouteWithoutRouteId(@TempDir Path dir) throws Exception { .contains("direct:next"); } + @Test + void structureHasTheChecksumTheRouteHasWhenItRuns(@TempDir Path dir) throws Exception { + Path orders = write(dir, "Orders.java", """ + import org.apache.camel.builder.RouteBuilder; + + public class Orders extends RouteBuilder { + @Override + public void configure() { + from("direct:orders").routeId("orders") + .choice() + .when(simple("${header.vip} == true")).to("seda:vip") + .otherwise().to("seda:orders") + .end() + .log("${body}"); + } + } + """); + StringPrinter printer = new StringPrinter(); + CamelRouteStructureAction command = new CamelRouteStructureAction(new CamelJBangMain().withPrinter(printer)); + command.name = orders.toString(); + command.jsonOutput = true; + + assertThat(command.doCall()).isZero(); + JsonObject structure = Jsoner.deserialize(printer.getOutput(), new JsonObject()); + JsonObject route = ((JsonArray) structure.getCollection("routes")).getMap(0); + String read = route.getString("checksum"); + + // the same route, compiled and added to a CamelContext as when it runs + try (CamelContext context = new DefaultCamelContext()) { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + from("direct:orders").routeId("orders") + .choice() + .when(simple("${header.vip} == true")).to("seda:vip") + .otherwise().to("seda:orders") + .end() + .log("${body}"); + } + }); + String running = ((ModelCamelContext) context).getRouteDefinition("orders").getChecksum(); + assertThat(read).isNotNull().isEqualTo(running); + } + + // the text shows it under the source + printer = new StringPrinter(); + command = new CamelRouteStructureAction(new CamelJBangMain().withPrinter(printer)); + command.name = orders.toString(); + assertThat(command.doCall()).isZero(); + assertThat(printer.getOutput()).contains("Source: Orders.java", "Checksum: " + read); + } + private static Path write(Path dir, String name, String content) throws Exception { Path file = dir.resolve(name); Files.writeString(file, content); From fc77a69bdf40b3841bd3414b4e67d688f0f20303 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 21 Oct 2026 08:21:02 +0000 Subject: [PATCH 3/6] Regen --- .../camel/catalog/dev-consoles-openapi.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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 865ca3654eca9..34df94abd74bf 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 @@ -5924,6 +5924,14 @@ "type": "string", "description": "The source that declares the templated route the route was created from (only present when known)" }, + "checksum": { + "type": "string", + "description": "The checksum of the route as written, the same in every run: compare it with an earlier run to tell whether the route changed (only present when known, as it needs camel-xml-io)" + }, + "revision": { + "type": "integer", + "description": "The revision of the route in this run: 1 when it is added, one more each time it is added again changed, such as by a reload (only present when the checksum is known)" + }, "state": { "type": "string", "description": "The route state" @@ -6678,6 +6686,10 @@ "type": "string", "description": "The source location (only present when known)" }, + "checksum": { + "type": "string", + "description": "The checksum of the route as written, the same in every run: the dump is of this version of the route (only present when known, as it needs camel-xml-io)" + }, "format": { "type": "string", "description": "The dump format, xml\/yaml\/java (only present when the dump succeeded)" @@ -6974,6 +6986,10 @@ "type": "string", "description": "The route description (only present when configured)" }, + "checksum": { + "type": "string", + "description": "The checksum of the route as written, the same in every run: the facts a tool keeps per line hold for this version of the route (only present when known, as it needs camel-xml-io)" + }, "code": { "type": "array", "items": { From f4147ca1144ad0515f4a197e5c7d6758eae6585b Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Wed, 21 Oct 2026 10:47:28 +0200 Subject: [PATCH 4/6] CAMEL-25771: Say "added again with another checksum" in the revision descriptions (address review) Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: Claus Ibsen --- .../org/apache/camel/catalog/dev-consoles-openapi.json | 2 +- .../resources/org/apache/camel/catalog/dev-consoles/route.json | 2 +- .../resources/META-INF/org/apache/camel/dev-console/route.json | 2 +- .../java/org/apache/camel/impl/console/RouteDevConsole.java | 2 +- .../apache/camel/api/management/mbean/ManagedRouteMBean.java | 2 +- 5 files changed, 5 insertions(+), 5 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 34df94abd74bf..6c30c57da90de 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 @@ -5930,7 +5930,7 @@ }, "revision": { "type": "integer", - "description": "The revision of the route in this run: 1 when it is added, one more each time it is added again changed, such as by a reload (only present when the checksum is known)" + "description": "The revision of the route in this run: 1 when it is added, one more each time it is added again with another checksum, such as by a reload (only present when the checksum is known)" }, "state": { "type": "string", diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route.json index abef3939b9e18..b927770390283 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route.json @@ -139,7 +139,7 @@ }, "revision": { "type": "integer", - "description": "The revision of the route in this run: 1 when it is added, one more each time it is added again changed, such as by a reload (only present when the checksum is known)" + "description": "The revision of the route in this run: 1 when it is added, one more each time it is added again with another checksum, such as by a reload (only present when the checksum is known)" }, "state": { "type": "string", diff --git a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route.json b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route.json index abef3939b9e18..b927770390283 100644 --- a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route.json +++ b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/route.json @@ -139,7 +139,7 @@ }, "revision": { "type": "integer", - "description": "The revision of the route in this run: 1 when it is added, one more each time it is added again changed, such as by a reload (only present when the checksum is known)" + "description": "The revision of the route in this run: 1 when it is added, one more each time it is added again with another checksum, such as by a reload (only present when the checksum is known)" }, "state": { "type": "string", diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java index aebdc51c97ecc..eb956478637cf 100644 --- a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java @@ -98,7 +98,7 @@ public record RouteEntry( @Metadata(description = "The source location (only present when known)") String source, @Metadata(description = "The source that declares the templated route the route was created from (only present when known)") String templatedRouteSource, @Metadata(description = "The checksum of the route as written, the same in every run: compare it with an earlier run to tell whether the route changed (only present when known, as it needs camel-xml-io)") String checksum, - @Metadata(description = "The revision of the route in this run: 1 when it is added, one more each time it is added again changed, such as by a reload (only present when the checksum is known)") Integer revision, + @Metadata(description = "The revision of the route in this run: 1 when it is added, one more each time it is added again with another checksum, such as by a reload (only present when the checksum is known)") Integer revision, @Metadata(description = "The route state") String state, @Metadata(description = "Whether the route supports suspension") boolean supportsSuspension, @Metadata(description = "Route uptime, human readable text") String uptime, diff --git a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java index 20a9f49777824..a2d9cb08e47de 100644 --- a/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java +++ b/core/camel-management-api/src/main/java/org/apache/camel/api/management/mbean/ManagedRouteMBean.java @@ -61,7 +61,7 @@ public interface ManagedRouteMBean extends ManagedPerformanceCounterMBean { @ManagedAttribute(description = "The checksum of the route as written, the same in every run: compare it to tell whether the route changed (null when camel-xml-io is not on the classpath)") String getChecksum(); - @ManagedAttribute(description = "The revision of the route in this run: 1 when it is added, one more each time it is added again changed, such as by a reload (0 when the checksum is not known)") + @ManagedAttribute(description = "The revision of the route in this run: 1 when it is added, one more each time it is added again with another checksum, such as by a reload (0 when the checksum is not known)") int getRevision(); @ManagedAttribute(description = "The number of steps that have been reached with more than one body type, across the messages (recorded while the message history is on)") From 1102549e4cd7c01f5a5b376c0557ee244198a157 Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Wed, 21 Oct 2026 22:17:40 +0200 Subject: [PATCH 5/6] CAMEL-25771: The checksum leaves out what templates, Kamelets and the endpoint DSL make from counters, masks URI secrets, and the revision is set before the route is announced (address review) - a route from a route template or a Kamelet: the node prefix, the id of a Kamelet route without one of its own, and the routeId of its kamelet:source and kamelet:sink, all made from a counter, are no part of the checksum - the hash an endpoint DSL builder adds for its options that are objects (an identity hash) is no part of it - the secrets in endpoint URIs are masked before they are hashed - the revision is set before RouteAddedEvent and onRoutesAdd, and the revisions of the 10000 route ids used last are kept - the new test classes are package-private Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: Claus Ibsen --- .../kamelet/KameletRouteChecksumTest.java | 72 +++++++++++++++ .../src/main/java/org/apache/camel/Route.java | 7 +- .../impl/engine/AbstractCamelContext.java | 39 +++++--- .../camel/impl/engine/RouteService.java | 5 ++ .../console/RouteChecksumDevConsoleTest.java | 4 +- .../apache/camel/impl/RouteChecksumTest.java | 88 +++++++++++++++++-- .../RouteWatcherReloadChangedRoutesTest.java | 6 +- .../management/ManagedRouteChecksumTest.java | 4 +- .../camel/xml/LwModelChecksumGenerator.java | 7 +- .../apache/camel/xml/LwModelToXMLDumper.java | 84 ++++++++++++++++-- .../xml/LwModelToXMLDumperRawUriTest.java | 42 +++++++++ .../modules/ROOT/pages/routes.adoc | 13 ++- .../EndpointDslRouteChecksumTest.java | 55 ++++++++++++ 13 files changed, 382 insertions(+), 44 deletions(-) create mode 100644 components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletRouteChecksumTest.java create mode 100644 core/camel-xml-io/src/test/java/org/apache/camel/xml/LwModelToXMLDumperRawUriTest.java create mode 100644 dsl/camel-endpointdsl/src/test/java/org/apache/camel/builder/endpoint/EndpointDslRouteChecksumTest.java diff --git a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletRouteChecksumTest.java b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletRouteChecksumTest.java new file mode 100644 index 0000000000000..a181aeaf6c949 --- /dev/null +++ b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletRouteChecksumTest.java @@ -0,0 +1,72 @@ +/* + * 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.component.kamelet; + +import java.util.Map; +import java.util.TreeMap; + +import org.apache.camel.CamelContext; +import org.apache.camel.Route; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.DefaultCamelContext; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * CAMEL-25771: a Kamelet route gets its node prefix, and its id when it has none of its own, from a counter, which the + * checksum leaves out: the same Kamelet route has the same checksum each time it is created. + */ +class KameletRouteChecksumTest { + + @Test + void theSameKameletRouteHasTheSameChecksumWhenCreatedAgain() throws Exception { + // the counter goes on in the second run, as it does when a reload creates the routes again + Map first = checksums(); + Map second = checksums(); + + assertEquals(2, first.size(), first.toString()); + first.values().forEach(checksum -> assertNotNull(checksum, first.toString())); + assertEquals(first, second); + } + + // the checksums of the Kamelet routes: the one with an id of its own, and the one without + private static Map checksums() throws Exception { + try (CamelContext context = new DefaultCamelContext()) { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + routeTemplate("greeting").templateParameter("name") + .from("kamelet:source") + .setBody().simple("Hello {{name}}"); + + from("direct:own").routeId("own").kamelet("greeting/greet-own?name=own"); + from("direct:none").routeId("none").kamelet("greeting?name=none"); + } + }); + context.start(); + Map answer = new TreeMap<>(); + for (Route route : context.getRoutes()) { + if ("greeting".equals(route.getRouteTemplateId())) { + answer.put("greet-own".equals(route.getId()) ? "own" : "none", route.getChecksum()); + } + } + return answer; + } + } +} diff --git a/core/camel-api/src/main/java/org/apache/camel/Route.java b/core/camel-api/src/main/java/org/apache/camel/Route.java index 745df07c73889..8470348028378 100644 --- a/core/camel-api/src/main/java/org/apache/camel/Route.java +++ b/core/camel-api/src/main/java/org/apache/camel/Route.java @@ -220,8 +220,9 @@ default Resource getTemplatedRouteResource() { /** * Gets the checksum of the route as written: its steps, options and expressions, with property placeholders as - * written, without the ids Camel generates and where the route is in its source. The same route gives the same - * checksum in every run, so tools can tell which routes changed between two runs. + * written, without the ids Camel generates and where the route is in its source, and with the secrets in endpoint + * URIs masked. The same route gives the same checksum in every run, so tools can tell which routes changed between + * two runs. * * @return the checksum, or null when it is not known (camel-xml-io is not on the classpath) * @see org.apache.camel.spi.ModelChecksumGenerator @@ -236,7 +237,7 @@ default String getChecksum() { * Gets the revision of the route in this run: 1 when the route is added, one more each time it is added again with * another checksum, such as when a reload changed it. A reload that brings the route back as it was keeps the * revision. The revision follows the route id, and starts over when the application restarts (tools compare - * checksums across runs). + * checksums across runs). Camel keeps the revisions of the 10000 route ids used last. * * @return the revision, or 0 when it is not known (the checksum is not known) * @since 4.24 diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java index 662d3545373a4..0bd7b42e86ed7 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java @@ -229,6 +229,8 @@ public abstract class AbstractCamelContext extends BaseService implements CatalogCamelContext, Suspendable { private static final Logger LOG = LoggerFactory.getLogger(AbstractCamelContext.class); + // how many route ids keep their revision (CAMEL-25771) + private static final int MAX_ROUTE_REVISIONS = 10000; private final InternalServiceManager internalServiceManager; @@ -252,8 +254,15 @@ public abstract class AbstractCamelContext extends BaseService private final StopWatch stopWatch = new StopWatch(false); private final ThreadLocal> componentsInCreation = ThreadLocal.withInitial(HashSet::new); private final Lock routesLock = new ReentrantLock(); - // the revisions of the routes by id, guarded by routesLock (CAMEL-25771) - private final Map routeRevisions = new HashMap<>(); + // the revisions of the routes by id, guarded by routesLock (CAMEL-25771): the ids used least recently go first + // once there are more than MAX_ROUTE_REVISIONS, so an application that keeps adding routes with new ids, such as + // Kamelet routes without an id of their own, does not grow it for the life of the JVM + private final Map routeRevisions = new LinkedHashMap<>(16, 0.75f, true) { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > MAX_ROUTE_REVISIONS; + } + }; private final Lock lock = new ReentrantLock(); private VetoCamelContextStartException vetoed; private String managementName; @@ -4780,6 +4789,7 @@ public void addRoute(Route route) { routesLock.lock(); try { routes.add(route); + // RouteService has set it before the route was announced; this is for a route added another way reviseRoute(route); } finally { routesLock.unlock(); @@ -4787,20 +4797,27 @@ public void addRoute(Route route) { } /** - * The revision of a route added: 1 the first time its id is added, one more when it is added again with another - * checksum (a reload that changed it), the same when it comes back as it was. It is kept by route id, so it - * outlives the route being removed and added again. + * Sets the revision of a route being added: 1 the first time its id is added, one more when it is added again with + * another checksum (a reload that changed it), the same when it comes back as it was. It is kept by route id, so it + * outlives the route being removed and added again; the {@value #MAX_ROUTE_REVISIONS} ids used last are kept. The + * same route revised again keeps its revision, so RouteService sets it before the route added event and + * {@link #addRoute(Route)} after it. */ - private void reviseRoute(Route route) { + void reviseRoute(Route route) { String checksum = route.getChecksum(); if (route.getId() == null || checksum == null || !(route instanceof DefaultRoute defaultRoute)) { return; } - RouteRevision previous = routeRevisions.get(route.getId()); - int revision = previous == null ? 1 - : previous.checksum().equals(checksum) ? previous.revision() : previous.revision() + 1; - routeRevisions.put(route.getId(), new RouteRevision(checksum, revision)); - defaultRoute.setRevision(revision); + routesLock.lock(); + try { + RouteRevision previous = routeRevisions.get(route.getId()); + int revision = previous == null ? 1 + : previous.checksum().equals(checksum) ? previous.revision() : previous.revision() + 1; + routeRevisions.put(route.getId(), new RouteRevision(checksum, revision)); + defaultRoute.setRevision(revision); + } finally { + routesLock.unlock(); + } } /** The checksum a route id was last added with, and its revision. */ diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/RouteService.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/RouteService.java index 02e8933fb2a09..7ecd5487182c7 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/RouteService.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/RouteService.java @@ -224,6 +224,11 @@ protected void doWarmUp() throws Exception { startChildServices(route, childServices); + // the revision before the route is announced, so listeners and JMX see it (CAMEL-25771) + if (camelContext instanceof AbstractCamelContext acc) { + acc.reviseRoute(route); + } + // fire event EventHelper.notifyRouteAdded(camelContext, route); } diff --git a/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteChecksumDevConsoleTest.java b/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteChecksumDevConsoleTest.java index fed414e196bbd..2075fe587691c 100644 --- a/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteChecksumDevConsoleTest.java +++ b/core/camel-console/src/test/java/org/apache/camel/impl/console/RouteChecksumDevConsoleTest.java @@ -29,7 +29,7 @@ * CAMEL-25771: the route consoles have the checksum of each route as written, and the route console its revision, so a * tool can tell which routes changed and which version of a route the structure or the dump is of. */ -public class RouteChecksumDevConsoleTest extends ContextTestSupport { +class RouteChecksumDevConsoleTest extends ContextTestSupport { @Override protected boolean useJmx() { @@ -48,7 +48,7 @@ public void configure() { } @Test - public void testChecksumInTheRouteConsoles() { + void testChecksumInTheRouteConsoles() { String checksum = context.getRoute("myRoute").getChecksum(); Assertions.assertNotNull(checksum); diff --git a/core/camel-core/src/test/java/org/apache/camel/impl/RouteChecksumTest.java b/core/camel-core/src/test/java/org/apache/camel/impl/RouteChecksumTest.java index 97270f86d9547..107d87bc0a244 100644 --- a/core/camel-core/src/test/java/org/apache/camel/impl/RouteChecksumTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/impl/RouteChecksumTest.java @@ -16,6 +16,8 @@ */ package org.apache.camel.impl; +import java.util.ArrayList; +import java.util.List; import java.util.Properties; import java.util.function.Consumer; @@ -24,6 +26,10 @@ import org.apache.camel.RoutesBuilder; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.builder.RouteConfigurationBuilder; +import org.apache.camel.builder.TemplatedRouteBuilder; +import org.apache.camel.spi.CamelEvent; +import org.apache.camel.spi.CamelEvent.RouteAddedEvent; +import org.apache.camel.support.EventNotifierSupport; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -35,7 +41,7 @@ * CAMEL-25771: each route has a checksum of the route as written, the same in every run, and a revision that counts the * times it came back changed in this run, so tools can tell which routes changed. */ -public class RouteChecksumTest { +class RouteChecksumTest { private static RouteBuilder orders(String target) { return new RouteBuilder() { @@ -71,7 +77,7 @@ private static String checksum(String routeId, Consumer setup, Rou } @Test - public void testSameRouteSameChecksumInEveryRun() throws Exception { + void testSameRouteSameChecksumInEveryRun() throws Exception { String first = checksum("orders", orders("mock:out")); String second = checksum("orders", orders("mock:out")); @@ -81,7 +87,7 @@ public void testSameRouteSameChecksumInEveryRun() throws Exception { } @Test - public void testEditedRouteNewChecksum() throws Exception { + void testEditedRouteNewChecksum() throws Exception { String before = checksum("orders", orders("mock:out")); assertNotEquals(before, checksum("orders", orders("mock:other"))); @@ -100,7 +106,7 @@ public void configure() { } @Test - public void testGeneratedIdsAndOtherRoutesLeftOut() throws Exception { + void testGeneratedIdsAndOtherRoutesLeftOut() throws Exception { String alone = checksum("orders", orders("mock:out")); // another route first: the ids Camel generates for the steps of orders are not the same String after = checksum("orders", new RouteBuilder() { @@ -114,7 +120,7 @@ public void configure() { } @Test - public void testRouteConfigurationsLeftOut() throws Exception { + void testRouteConfigurationsLeftOut() throws Exception { String plain = checksum("orders", orders("mock:out")); // the onException of a route configuration is merged into every route, which are not changed by it String configured = checksum("orders", new RouteConfigurationBuilder() { @@ -141,7 +147,7 @@ public void configure() { } @Test - public void testPlaceholdersAsWritten() throws Exception { + void testPlaceholdersAsWritten() throws Exception { String a = checksum("orders", context -> context.getPropertiesComponent().setInitialProperties(props("mock:a")), orders("{{target}}")); String b = checksum("orders", context -> context.getPropertiesComponent().setInitialProperties(props("mock:b")), @@ -152,7 +158,7 @@ public void testPlaceholdersAsWritten() throws Exception { } @Test - public void testSourceLocationsLeftOutAlsoWhileDebugging() throws Exception { + void testSourceLocationsLeftOutAlsoWhileDebugging() throws Exception { String plain = checksum("orders", orders("mock:out")); String debugging = checksum("orders", context -> { context.setSourceLocationEnabled(true); @@ -163,7 +169,7 @@ public void testSourceLocationsLeftOutAlsoWhileDebugging() throws Exception { } @Test - public void testRevisionCountsTheChanges() throws Exception { + void testRevisionCountsTheChanges() throws Exception { try (CamelContext context = new DefaultCamelContext()) { context.addRoutes(orders("mock:out")); context.start(); @@ -185,6 +191,72 @@ public void testRevisionCountsTheChanges() throws Exception { } } + @Test + void testTemplatedRouteSameChecksumWhenCreatedAgain() throws Exception { + try (CamelContext context = new DefaultCamelContext()) { + context.addRoutes(new RouteBuilder() { + @Override + public void configure() { + routeTemplate("orders-template").templateParameter("target") + .from("direct:orders") + .split(body()) + .to("{{target}}"); + } + }); + context.start(); + + String first = addOrders(context); + assertEquals(1, context.getRoute("orders").getRevision()); + + // created again, as a reload does: the node prefix comes from a counter, which is no part of the checksum + context.getRouteController().stopRoute("orders"); + context.removeRoute("orders"); + String second = addOrders(context); + + assertNotNull(first); + assertEquals(first, second); + assertEquals(1, context.getRoute("orders").getRevision()); + } + } + + private static String addOrders(CamelContext context) { + TemplatedRouteBuilder.builder(context, "orders-template").routeId("orders").parameter("target", "mock:out") + .add(); + return context.getRoute("orders").getChecksum(); + } + + @Test + void testSecretsInUrisMasked() throws Exception { + // the checksum is printed beside the masked URIs: it must not tell one literal password from another + String a = checksum("orders", orders("stub:http://localhost/orders?password=secret1")); + String b = checksum("orders", orders("stub:http://localhost/orders?password=secret2")); + String other = checksum("orders", orders("stub:http://localhost/other?password=secret1")); + + assertEquals(a, b); + assertNotEquals(a, other); + } + + @Test + void testRevisionKnownWhenTheRouteIsAnnounced() throws Exception { + List revisions = new ArrayList<>(); + try (CamelContext context = new DefaultCamelContext()) { + context.getManagementStrategy().addEventNotifier(new EventNotifierSupport() { + @Override + public void notify(CamelEvent event) { + if (event instanceof RouteAddedEvent added) { + revisions.add(added.getRoute().getRevision()); + } + } + }); + context.addRoutes(orders("mock:out")); + context.start(); + replace(context, orders("mock:other")); + } + + // 0 would say the checksum is not known + assertEquals(List.of(1, 2), revisions); + } + private static void replace(CamelContext context, RoutesBuilder builder) throws Exception { context.getRouteController().stopRoute("orders"); context.removeRoute("orders"); diff --git a/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadChangedRoutesTest.java b/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadChangedRoutesTest.java index 6df62cd9b8307..ce3dd8d7fb9d2 100644 --- a/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadChangedRoutesTest.java +++ b/core/camel-core/src/test/java/org/apache/camel/support/RouteWatcherReloadChangedRoutesTest.java @@ -50,7 +50,7 @@ * CAMEL-25771: the summary of a reload says how many routes the reload changed, and marks them: a route that came back * with another checksum is changed, one that was not there before is new. */ -public class RouteWatcherReloadChangedRoutesTest extends ContextTestSupport { +class RouteWatcherReloadChangedRoutesTest extends ContextTestSupport { @TempDir Path dir; @@ -104,7 +104,7 @@ public void append(LogEvent event) { } @AfterEach - public void stopCapture() { + void stopCapture() { strategy.stop(); LoggerContext logging = (LoggerContext) LogManager.getContext(false); logging.getConfiguration().getRootLogger().removeAppender(appender.getName()); @@ -113,7 +113,7 @@ public void stopCapture() { } @Test - public void theSummarySaysWhichRoutesTheReloadChanged() throws Exception { + void theSummarySaysWhichRoutesTheReloadChanged() throws Exception { // the file now sends the orders elsewhere and has a new route; the stock route is as it was loader.next = routes("mock:other", true); diff --git a/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteChecksumTest.java b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteChecksumTest.java index e0d1da041d9ad..a3b0d87ceada9 100644 --- a/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteChecksumTest.java +++ b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRouteChecksumTest.java @@ -33,10 +33,10 @@ * CAMEL-25771: a managed route has the checksum of the route as written, and its revision in this run. */ @DisabledOnOs(OS.AIX) -public class ManagedRouteChecksumTest extends ManagementTestSupport { +class ManagedRouteChecksumTest extends ManagementTestSupport { @Test - public void testChecksumAndRevision() throws Exception { + void testChecksumAndRevision() throws Exception { MBeanServer mbeanServer = getMBeanServer(); ObjectName on = getCamelObjectName(TYPE_ROUTE, "myRoute"); diff --git a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelChecksumGenerator.java b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelChecksumGenerator.java index 16b0d4aa9724b..208c3d9566954 100644 --- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelChecksumGenerator.java +++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelChecksumGenerator.java @@ -34,8 +34,9 @@ /** * {@link ModelChecksumGenerator} based on the generated {@link ModelWriter}, which writes every option of the model: - * the SHA-256 of the definition as XML, with property placeholders as written, without the ids Camel generates and - * without source locations (also while debugging). The first 12 hex characters of it are the checksum. + * the SHA-256 of the definition as XML, with property placeholders as written, without the ids Camel generates (also + * those a route template or Kamelet makes from a counter), without source locations (also while debugging), and with + * the secrets in endpoint URIs masked. The first 12 hex characters of it are the checksum. */ @JdkService(ModelChecksumGenerator.FACTORY) public class LwModelChecksumGenerator implements ModelChecksumGenerator { @@ -51,7 +52,7 @@ public String generateChecksum(CamelContext context, NamedNode definition) { MessageDigest digest = MessageDigest.getInstance("SHA-256"); try (Writer out = new OutputStreamWriter( new DigestOutputStream(OutputStream.nullOutputStream(), digest), StandardCharsets.UTF_8)) { - LwModelToXMLDumper.write(context, definition, out, false, false, false); + LwModelToXMLDumper.write(context, definition, out, false, false, false, true); } return HexFormat.of().formatHex(digest.digest(), 0, CHECKSUM_BYTES); } catch (Exception e) { diff --git a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java index 04a7e00d79620..a219d97c5046c 100644 --- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java +++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java @@ -21,12 +21,18 @@ import java.io.Writer; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; @@ -55,6 +61,7 @@ import org.apache.camel.spi.annotations.JdkService; import org.apache.camel.util.KeyValueHolder; import org.apache.camel.util.StringHelper; +import org.apache.camel.util.URISupport; import org.apache.camel.xml.out.BaseWriter; import org.apache.camel.xml.out.ModelWriter; @@ -78,29 +85,40 @@ public String dumpModelAsXml( throws Exception { StringWriter buffer = new StringWriter(); // while debugging the source locations are written too, for the debugger to show where a step is - write(context, definition, buffer, resolvePlaceholders, generatedIds, sourceLocation || context.isDebugging()); + write(context, definition, buffer, resolvePlaceholders, generatedIds, sourceLocation || context.isDebugging(), + false); return buffer.toString(); } /** * Writes the definition as XML: the dump, and the text the checksum of a route is computed from * ({@link LwModelChecksumGenerator}), which never has the source locations. + * + * @param checksum whether the text is for the checksum: then what Camel makes from a counter or an identity hash is + * left out (the node prefix and the route id that a route template or Kamelet makes, the hash of + * the endpoint DSL options that are objects), and the secrets in endpoint URIs are masked */ static void write( CamelContext context, NamedNode definition, Writer out, boolean resolvePlaceholders, boolean generatedIds, - boolean sourceLocation) + boolean sourceLocation, boolean checksum) throws Exception { Properties properties = new Properties(); Map namespaces = new LinkedHashMap<>(); Map> locations = new HashMap<>(); List restorers = new ArrayList<>(); + // the definitions whose id Camel makes from a counter, though it is marked as custom, and those ids + Set> counterIds = Collections.newSetFromMap(new IdentityHashMap<>()); + Set counterRouteIds = new HashSet<>(); Consumer extractor = route -> { extractNamespaces(route, namespaces); if (sourceLocation) { extractSourceLocations(route, locations); } - restorers.add(resolveEndpointDslUris(route)); + restorers.add(resolveEndpointDslUris(route, checksum)); + if (checksum) { + restorers.add(leaveOutCounters(route, counterIds, counterRouteIds)); + } if (Boolean.TRUE.equals(route.isTemplate())) { Map parameters = route.getTemplateParameters(); if (parameters != null) { @@ -114,7 +132,7 @@ static void write( protected void doWriteOptionalIdentifiedDefinitionAttributes(OptionalIdentifiedDefinition def) throws IOException { - if (generatedIds || Boolean.TRUE.equals(def.getCustomId())) { + if ((generatedIds || Boolean.TRUE.equals(def.getCustomId())) && !counterIds.contains(def)) { // write id doWriteAttribute("id", def.getId()); } @@ -195,6 +213,15 @@ protected void attribute(String name, Object value) throws IOException { if (resolvePlaceholders && value != null) { value = resolve(value.toString(), properties); } + if (checksum && "uri".equals(name) && value != null) { + String uri = value.toString(); + for (String id : counterRouteIds) { + // kamelet:source and kamelet:sink have the id of their route + uri = withoutParameter(uri, "routeId", Pattern.quote(id)); + } + // a literal password is no part of the checksum, which is printed beside the masked URIs + value = URISupport.sanitizeUri(uri); + } super.attribute(name, value); } @@ -332,25 +359,66 @@ private static void extractSourceLocations(RouteDefinition route, Map> counterIds, Set counterRouteIds) { + if (!Boolean.TRUE.equals(route.isTemplate())) { + return () -> { + }; + } + String prefix = route.getNodePrefixId(); + if (prefix != null && Boolean.TRUE.equals(route.isKamelet()) && route.getId() != null + && route.getId().endsWith("-" + prefix)) { + counterIds.add(route); + counterRouteIds.add(route.getId()); + } + route.setNodePrefixId(null); + return () -> route.setNodePrefixId(prefix); + } + /** * If the route has been built with endpoint-dsl, then the model will not have uri set which then cannot be included * in the JAXB model dump. Returns a {@link Runnable} that restores the original endpoint DSL builders after the XML * has been written, so that the live {@link RouteDefinition} is not permanently mutated by the dump. */ @SuppressWarnings("rawtypes") - private static Runnable resolveEndpointDslUris(RouteDefinition route) { + private static Runnable resolveEndpointDslUris(RouteDefinition route, boolean checksum) { List restorers = new ArrayList<>(); FromDefinition from = route.getInput(); if (from != null && from.getEndpointConsumerBuilder() != null) { EndpointConsumerBuilder builder = from.getEndpointConsumerBuilder(); - from.setUri(builder.getRawUri()); + from.setUri(rawUri(builder.getRawUri(), checksum)); restorers.add(() -> from.setEndpointConsumerBuilder(builder)); } Collection col = filterTypeInOutputs(route.getOutputs(), SendDefinition.class); for (SendDefinition to : col) { if (to.getEndpointProducerBuilder() != null) { EndpointProducerBuilder builder = to.getEndpointProducerBuilder(); - to.setUri(builder.getRawUri()); + to.setUri(rawUri(builder.getRawUri(), checksum)); restorers.add(() -> to.setEndpointProducerBuilder(builder)); } } @@ -358,7 +426,7 @@ private static Runnable resolveEndpointDslUris(RouteDefinition route) { for (ToDynamicDefinition to : col2) { if (to.getEndpointProducerBuilder() != null) { EndpointProducerBuilder builder = to.getEndpointProducerBuilder(); - to.setUri(builder.getRawUri()); + to.setUri(rawUri(builder.getRawUri(), checksum)); // ToDynamicDefinition.setUri does not call clear(), so the builder is preserved; // only the uri field needs to be reset. restorers.add(() -> to.setUri(null)); diff --git a/core/camel-xml-io/src/test/java/org/apache/camel/xml/LwModelToXMLDumperRawUriTest.java b/core/camel-xml-io/src/test/java/org/apache/camel/xml/LwModelToXMLDumperRawUriTest.java new file mode 100644 index 0000000000000..ab27303a5eb66 --- /dev/null +++ b/core/camel-xml-io/src/test/java/org/apache/camel/xml/LwModelToXMLDumperRawUriTest.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.xml; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * CAMEL-25771: the hash an endpoint DSL builder adds for its options that are objects is an identity hash, another one + * in every run, so the checksum of a route leaves it out. + */ +class LwModelToXMLDumperRawUriTest { + + @Test + void theHashOfTheObjectOptionsIsLeftOutOfTheChecksum() { + assertEquals("sql:select?a=1&b=2", LwModelToXMLDumper.rawUri("sql:select?a=1&hash=1f2e3d&b=2", true)); + assertEquals("sql:select?b=2", LwModelToXMLDumper.rawUri("sql:select?hash=1f2e3d&b=2", true)); + assertEquals("sql:select?a=1", LwModelToXMLDumper.rawUri("sql:select?a=1&hash=1f2e3d", true)); + assertEquals("sql:select", LwModelToXMLDumper.rawUri("sql:select?hash=1f2e3d", true)); + assertEquals("sql:select?ahash=1f2e3d", LwModelToXMLDumper.rawUri("sql:select?ahash=1f2e3d", true)); + } + + @Test + void theDumpKeepsTheHash() { + assertEquals("sql:select?a=1&hash=1f2e3d", LwModelToXMLDumper.rawUri("sql:select?a=1&hash=1f2e3d", false)); + } +} diff --git a/docs/user-manual/modules/ROOT/pages/routes.adoc b/docs/user-manual/modules/ROOT/pages/routes.adoc index 757ef2c6d6a4e..799e585f1e9a5 100644 --- a/docs/user-manual/modules/ROOT/pages/routes.adoc +++ b/docs/user-manual/modules/ROOT/pages/routes.adoc @@ -268,15 +268,19 @@ in every run. The checksum is a short hash (12 hex characters) of the whole route: its steps, their options and expressions, with property placeholders as written. It leaves out what does not change the route: -* the ids Camel generates (a route or step with an id of its own keeps it in the checksum); +* the ids Camel generates (a route or step with an id of its own keeps it in the checksum), also the node prefix that a +route template or a Kamelet gives the routes it creates, and the id of a Kamelet route without one of its own: the same +templated route has the same checksum each time it is created; * where the route is in its source: moving the route in its file, or adding a route above it, keeps its checksum; * the parts that route configurations add to every route (their `onException`, `intercept` and `onCompletion`): a -change in a shared route configuration does not change every route. The `onException` of a route itself is part of it. +change in a shared route configuration does not change every route. The `onException` of a route itself is part of it; +* the secrets in endpoint URIs, such as a password written in the URI, which are masked as in the logs. Each route also has a _revision_ in the running application: it is 1 when the route is added, and one more each time the route is added again with another checksum, such as when xref:route-reload.adoc[route reloading] changed it. A reload that brings the route back as it was keeps its revision. The revision follows the route id, and starts over -when the application restarts: to compare two runs, compare the checksums. +when the application restarts: to compare two runs, compare the checksums. Camel keeps the revisions of the 10000 route +ids used last. The checksum and revision are on the `Route` (`getChecksum()` and `getRevision()`), on the route in JMX (the `Checksum` and `Revision` attributes), in the `route` developer console, and in the Camel CLI @@ -289,7 +293,8 @@ The checksum needs `camel-xml-io` on the classpath, which comes with `camel-core CLI connector. Without it the routes have no checksum, and no revision. NOTE: The checksum covers the route, not the code it calls: changing a Java lambda in `.process(...)`, or a bean the route -calls, does not change it. Neither does a property value the route uses, which is configuration, not another route. A +calls, does not change it. Neither does a property value the route uses, which is configuration, not another route, nor +an object given to an endpoint DSL option, such as a `DataSource`, which is a bean. A route without an id of its own gets an id such as `route3`, which can move to another route when routes are added to or removed from its file: match such routes by their checksum. diff --git a/dsl/camel-endpointdsl/src/test/java/org/apache/camel/builder/endpoint/EndpointDslRouteChecksumTest.java b/dsl/camel-endpointdsl/src/test/java/org/apache/camel/builder/endpoint/EndpointDslRouteChecksumTest.java new file mode 100644 index 0000000000000..c4e553f4b8fdc --- /dev/null +++ b/dsl/camel-endpointdsl/src/test/java/org/apache/camel/builder/endpoint/EndpointDslRouteChecksumTest.java @@ -0,0 +1,55 @@ +/* + * 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.builder.endpoint; + +import org.apache.camel.CamelContext; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.support.processor.DefaultExchangeFormatter; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * CAMEL-25771: an endpoint DSL option that is an object, such as an ExchangeFormatter, is a bean, which the checksum of + * a route leaves out: the same route has the same checksum with another instance. + */ +class EndpointDslRouteChecksumTest { + + @Test + void anOptionThatIsAnObjectKeepsTheChecksum() throws Exception { + String first = checksum(); + String second = checksum(); + + assertNotNull(first); + assertEquals(first, second); + } + + private static String checksum() throws Exception { + try (CamelContext context = new DefaultCamelContext()) { + context.addRoutes(new EndpointRouteBuilder() { + @Override + public void configure() { + from(direct("orders")).routeId("orders") + .to(log("out").advanced().exchangeFormatter(new DefaultExchangeFormatter())); + } + }); + context.start(); + return context.getRoute("orders").getChecksum(); + } + } +} From fff625a36ea1385f9e647229d96fc732ce678070 Mon Sep 17 00:00:00 2001 From: Claus Ibsen Date: Thu, 22 Oct 2026 07:53:37 +0200 Subject: [PATCH 6/6] CAMEL-25771: Compile the hash and routeId patterns of the checksum once The routeId pattern captures the value, which is compared to the id, and the & after a parameter is no part of the match, so a routeId of another value does not hide the one after it. Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: Claus Ibsen --- .../apache/camel/xml/LwModelToXMLDumper.java | 36 +++++++++++++------ .../xml/LwModelToXMLDumperRawUriTest.java | 10 ++++++ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java index a219d97c5046c..8dc9459b7c95b 100644 --- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java +++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/LwModelToXMLDumper.java @@ -73,6 +73,10 @@ @JdkService(ModelToXMLDumper.FACTORY) public class LwModelToXMLDumper implements ModelToXMLDumper { + // the separator before a query parameter, and its value; the & after it is not part of the match + private static final Pattern HASH_PARAMETER = Pattern.compile("([?&])hash=([0-9a-f]+)(?=&|$)"); + private static final Pattern ROUTE_ID_PARAMETER = Pattern.compile("([?&])routeId=([^&]*)(?=&|$)"); + @Override public String dumpModelAsXml(CamelContext context, NamedNode definition) throws Exception { return dumpModelAsXml(context, definition, false, true, false); @@ -217,7 +221,7 @@ protected void attribute(String name, Object value) throws IOException { String uri = value.toString(); for (String id : counterRouteIds) { // kamelet:source and kamelet:sink have the id of their route - uri = withoutParameter(uri, "routeId", Pattern.quote(id)); + uri = withoutRouteId(uri, id); } // a literal password is no part of the checksum, which is printed beside the masked URIs value = URISupport.sanitizeUri(uri); @@ -364,18 +368,30 @@ private static void extractSourceLocations(RouteDefinition route, Map