This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new 6981adc0a4b CAMEL-18556: camel-jbang - DevConsole should be started when used for Camel CLI 6981adc0a4b is described below commit 6981adc0a4b6ae8004fc03a51b739ecd3597d47b Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Mon Sep 26 17:16:00 2022 +0200 CAMEL-18556: camel-jbang - DevConsole should be started when used for Camel CLI --- .../impl/engine/DefaultDevConsoleResolver.java | 8 +- .../impl/console/DefaultDevConsoleRegistry.java | 2 + .../apache/camel/impl/console/EventConsole.java | 12 +- .../camel/cli/connector/LocalCliConnector.java | 81 +++++----- .../dsl/jbang/core/commands/CamelJBangMain.java | 2 + .../dsl/jbang/core/commands/process/ListEvent.java | 168 +++++++++++++++++++++ 6 files changed, 229 insertions(+), 44 deletions(-) diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultDevConsoleResolver.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultDevConsoleResolver.java index 695de1ed992..c2dd15b2a1d 100644 --- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultDevConsoleResolver.java +++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultDevConsoleResolver.java @@ -72,6 +72,8 @@ public class DefaultDevConsoleResolver implements DevConsoleResolver, CamelConte if (DevConsole.class.isAssignableFrom(type)) { answer = (DevConsole) camelContext.getInjector().newInstance(type, false); CamelContextAware.trySetCamelContext(answer, camelContext); + // ensure console is started + } else { throw new IllegalArgumentException( "Resolving dev-console: " + id + " detected type conflict: Not a DevConsole implementation. Found: " @@ -92,6 +94,10 @@ public class DefaultDevConsoleResolver implements DevConsoleResolver, CamelConte @Override public Optional<DevConsole> lookupDevConsole(String id) { DevConsoleRegistry dcr = camelContext.getExtension(DevConsoleRegistry.class); - return dcr.getConsole(id); + if (dcr != null) { + return dcr.getConsole(id); + } else { + return Optional.empty(); + } } } diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/DefaultDevConsoleRegistry.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/DefaultDevConsoleRegistry.java index 7b97e6a7f96..c84337d47cc 100644 --- a/core/camel-console/src/main/java/org/apache/camel/impl/console/DefaultDevConsoleRegistry.java +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/DefaultDevConsoleRegistry.java @@ -138,6 +138,8 @@ public class DefaultDevConsoleRegistry extends ServiceSupport implements DevCons result = consoles.add(console); if (result) { CamelContextAware.trySetCamelContext(console, camelContext); + // ensure console is started as it may be registered later + ServiceHelper.startService(console); LOG.debug("DevConsole with id {} successfully registered", console.getId()); } return result; diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/EventConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/EventConsole.java index 67e1195e623..57b2b9fee66 100644 --- a/core/camel-console/src/main/java/org/apache/camel/impl/console/EventConsole.java +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/EventConsole.java @@ -74,7 +74,7 @@ public class EventConsole extends AbstractDevConsole { protected String doCallText(Map<String, Object> options) { StringBuilder sb = new StringBuilder(); - if (!events.isEmpty()) { + if (events != null && !events.isEmpty()) { sb.append(String.format("Last %s Camel Events:", events.size())); for (CamelEvent event : events) { if (event.getTimestamp() > 0) { @@ -85,7 +85,7 @@ public class EventConsole extends AbstractDevConsole { } sb.append("\n"); } - if (!routeEvents.isEmpty()) { + if (routeEvents != null && !routeEvents.isEmpty()) { sb.append("\n"); sb.append(String.format("Last %s Route Events:", routeEvents.size())); for (CamelEvent.RouteEvent event : routeEvents) { @@ -97,7 +97,7 @@ public class EventConsole extends AbstractDevConsole { } sb.append("\n"); } - if (!exchangeEvents.isEmpty()) { + if (exchangeEvents != null && !exchangeEvents.isEmpty()) { sb.append("\n"); sb.append(String.format("Last %s Exchange Events:", exchangeEvents.size())); for (CamelEvent.ExchangeEvent event : exchangeEvents) { @@ -116,7 +116,7 @@ public class EventConsole extends AbstractDevConsole { protected JsonObject doCallJson(Map<String, Object> options) { JsonObject root = new JsonObject(); - if (!events.isEmpty()) { + if (events != null && !events.isEmpty()) { List<JsonObject> arr = new ArrayList<>(); for (CamelEvent event : events) { JsonObject jo = new JsonObject(); @@ -129,7 +129,7 @@ public class EventConsole extends AbstractDevConsole { } root.put("events", arr); } - if (!routeEvents.isEmpty()) { + if (routeEvents != null && !routeEvents.isEmpty()) { List<JsonObject> arr = new ArrayList<>(); for (CamelEvent event : routeEvents) { JsonObject jo = new JsonObject(); @@ -142,7 +142,7 @@ public class EventConsole extends AbstractDevConsole { } root.put("routeEvents", arr); } - if (!exchangeEvents.isEmpty()) { + if (exchangeEvents != null && !exchangeEvents.isEmpty()) { List<JsonObject> arr = new ArrayList<>(); for (CamelEvent.ExchangeEvent event : exchangeEvents) { JsonObject jo = new JsonObject(); diff --git a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java index 64cb2e9f0eb..62b5243fba9 100644 --- a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java +++ b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java @@ -42,6 +42,7 @@ import org.apache.camel.ExtendedCamelContext; import org.apache.camel.Route; import org.apache.camel.api.management.ManagedCamelContext; import org.apache.camel.console.DevConsole; +import org.apache.camel.console.DevConsoleRegistry; import org.apache.camel.spi.CliConnector; import org.apache.camel.spi.CliConnectorFactory; import org.apache.camel.spi.ContextReloadStrategy; @@ -256,24 +257,21 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C mcc.getManagedCamelContext().reset(true); } } else if ("thread-dump".equals(action)) { - DevConsole dc = camelContext.adapt(ExtendedCamelContext.class) - .getDevConsoleResolver().resolveDevConsole("thread"); + DevConsole dc = camelContext.getExtension(DevConsoleRegistry.class).resolveById("thread"); if (dc != null) { JsonObject json = (JsonObject) dc.call(DevConsole.MediaType.JSON, Map.of("stackTrace", "true")); LOG.trace("Updating output file: {}", outputFile); IOHelper.writeText(json.toJson(), outputFile); } } else if ("top-processors".equals(action)) { - DevConsole dc = camelContext.adapt(ExtendedCamelContext.class) - .getDevConsoleResolver().resolveDevConsole("top"); + DevConsole dc = camelContext.getExtension(DevConsoleRegistry.class).resolveById("top"); if (dc != null) { JsonObject json = (JsonObject) dc.call(DevConsole.MediaType.JSON, Map.of(Exchange.HTTP_PATH, "/*")); LOG.trace("Updating output file: {}", outputFile); IOHelper.writeText(json.toJson(), outputFile); } } else if ("source".equals(action)) { - DevConsole dc = camelContext.adapt(ExtendedCamelContext.class) - .getDevConsoleResolver().resolveDevConsole("source"); + DevConsole dc = camelContext.getExtension(DevConsoleRegistry.class).resolveById("source"); if (dc != null) { String filter = root.getString("filter"); JsonObject json = (JsonObject) dc.call(DevConsole.MediaType.JSON, Map.of("filter", filter)); @@ -336,38 +334,47 @@ public class LocalCliConnector extends ServiceSupport implements CliConnector, C } root.put("runtime", rc); - // collect details via console - DevConsole dc = camelContext.adapt(ExtendedCamelContext.class) - .getDevConsoleResolver().resolveDevConsole("context"); - DevConsole dc2 = camelContext.adapt(ExtendedCamelContext.class) - .getDevConsoleResolver().resolveDevConsole("route"); - if (dc != null && dc2 != null) { - JsonObject json = (JsonObject) dc.call(DevConsole.MediaType.JSON); - JsonObject json2 = (JsonObject) dc2.call(DevConsole.MediaType.JSON, Map.of("processors", "true")); - if (json != null && json2 != null) { - root.put("context", json); - root.put("routes", json2.get("routes")); + DevConsoleRegistry dcr = camelContext.getExtension(DevConsoleRegistry.class); + if (dcr != null) { + // collect details via console + DevConsole dc = dcr.resolveById("context"); + DevConsole dc2 = dcr.resolveById("route"); + if (dc != null && dc2 != null) { + JsonObject json = (JsonObject) dc.call(DevConsole.MediaType.JSON); + JsonObject json2 = (JsonObject) dc2.call(DevConsole.MediaType.JSON, Map.of("processors", "true")); + if (json != null && json2 != null) { + root.put("context", json); + root.put("routes", json2.get("routes")); + } } - } - DevConsole dc3 = camelContext.adapt(ExtendedCamelContext.class) - .getDevConsoleResolver().resolveDevConsole("endpoint"); - if (dc3 != null) { - JsonObject json = (JsonObject) dc3.call(DevConsole.MediaType.JSON); - root.put("endpoints", json); - } - DevConsole dc4 = camelContext.adapt(ExtendedCamelContext.class) - .getDevConsoleResolver().resolveDevConsole("health"); - if (dc4 != null) { - // include full details in health checks - JsonObject json = (JsonObject) dc4.call(DevConsole.MediaType.JSON, Map.of("exposureLevel", "full")); - root.put("healthChecks", json); - } - DevConsole dc5 = camelContext.adapt(ExtendedCamelContext.class) - .getDevConsoleResolver().resolveDevConsole("log"); - if (dc5 != null) { - JsonObject json = (JsonObject) dc5.call(DevConsole.MediaType.JSON); - if (json != null && !json.isEmpty()) { - root.put("logger", json); + DevConsole dc3 = dcr.resolveById("endpoint"); + if (dc3 != null) { + JsonObject json = (JsonObject) dc3.call(DevConsole.MediaType.JSON); + if (json != null && !json.isEmpty()) { + root.put("endpoints", json); + } + } + DevConsole dc4 = dcr.resolveById("health"); + if (dc4 != null) { + // include full details in health checks + JsonObject json = (JsonObject) dc4.call(DevConsole.MediaType.JSON, Map.of("exposureLevel", "full")); + if (json != null && !json.isEmpty()) { + root.put("healthChecks", json); + } + } + DevConsole dc5 = dcr.resolveById("event"); + if (dc5 != null) { + JsonObject json = (JsonObject) dc5.call(DevConsole.MediaType.JSON); + if (json != null && !json.isEmpty()) { + root.put("events", json); + } + } + DevConsole dc6 = dcr.resolveById("log"); + if (dc6 != null) { + JsonObject json = (JsonObject) dc6.call(DevConsole.MediaType.JSON); + if (json != null && !json.isEmpty()) { + root.put("logger", json); + } } } // various details diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java index 3bd66b67e3c..681343cf276 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java @@ -48,6 +48,7 @@ import org.apache.camel.dsl.jbang.core.commands.process.CamelStatus; import org.apache.camel.dsl.jbang.core.commands.process.CamelTop; import org.apache.camel.dsl.jbang.core.commands.process.Hawtio; import org.apache.camel.dsl.jbang.core.commands.process.Jolokia; +import org.apache.camel.dsl.jbang.core.commands.process.ListEvent; import org.apache.camel.dsl.jbang.core.commands.process.ListProcess; import org.apache.camel.dsl.jbang.core.commands.process.ListService; import org.apache.camel.dsl.jbang.core.commands.process.ListVault; @@ -71,6 +72,7 @@ public class CamelJBangMain implements Callable<Integer> { .addSubcommand("route", new CommandLine(new CamelRouteStatus(main))) .addSubcommand("processor", new CommandLine(new CamelProcessorStatus(main))) .addSubcommand("endpoint", new CommandLine(new CamelEndpointStatus(main))) + .addSubcommand("event", new CommandLine(new ListEvent(main))) .addSubcommand("service", new CommandLine(new ListService(main))) .addSubcommand("source", new CommandLine(new CamelSourceAction(main))) .addSubcommand("vault", new CommandLine(new ListVault(main)))) diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListEvent.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListEvent.java new file mode 100644 index 00000000000..c114e3a9c18 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListEvent.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.jbang.core.commands.process; + +import com.github.freva.asciitable.AsciiTable; +import com.github.freva.asciitable.Column; +import com.github.freva.asciitable.HorizontalAlign; +import com.github.freva.asciitable.OverflowBehaviour; +import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; +import org.apache.camel.util.TimeUtils; +import org.apache.camel.util.json.JsonArray; +import org.apache.camel.util.json.JsonObject; +import picocli.CommandLine; +import picocli.CommandLine.Command; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Command(name = "event", aliases = { "event", "events" }, + description = "Get latest 25 events of Camel integrations") +public class ListEvent extends ProcessBaseCommand { + + @CommandLine.Parameters(description = "Name or pid of running Camel integration", arity = "0..1") + String name = "*"; + + @CommandLine.Option(names = { "--sort" }, + description = "Sort by pid, name or age", defaultValue = "pid") + String sort; + + @CommandLine.Option(names = { "--filter" }, + description = "Filter event by event type: context, route, or exchange") + String filter; + + public ListEvent(CamelJBangMain main) { + super(main); + } + + @Override + public Integer call() throws Exception { + List<Row> rows = new ArrayList<>(); + + List<Long> pids = findPids(name); + ProcessHandle.allProcesses() + .filter(ph -> pids.contains(ph.pid())) + .forEach(ph -> { + JsonObject root = loadStatus(ph.pid()); + // there must be a status file for the running Camel integration + if (root != null) { + Row row = new Row(); + JsonObject context = (JsonObject) root.get("context"); + row.name = context.getString("name"); + if ("CamelJBang".equals(row.name)) { + row.name = extractName(root, ph); + } + row.pid = "" + ph.pid(); + row.uptime = extractSince(ph); + row.age = TimeUtils.printSince(row.uptime); + + if (filter == null || filter.contains("context")) { + fetchEvents(root, row, "events", rows); + } + if (filter == null || filter.contains("route")) { + fetchEvents(root, row, "routeEvents", rows); + } + if (filter == null || filter.contains("exchange")) { + fetchEvents(root, row, "exchangeEvents", rows); + } + } + }); + + // sort rows + rows.sort(this::sortRow); + + if (!rows.isEmpty()) { + System.out.println(AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, Arrays.asList( + new Column().header("PID").headerAlign(HorizontalAlign.CENTER).with(r -> r.pid), + new Column().header("NAME").dataAlign(HorizontalAlign.LEFT).maxWidth(30, OverflowBehaviour.ELLIPSIS_RIGHT) + .with(r -> r.name), + new Column().header("TYPE").dataAlign(HorizontalAlign.LEFT).with(r -> r.type), + new Column().header("AGE").dataAlign(HorizontalAlign.RIGHT).with(this::getTimestamp), + new Column().header("MESSAGE").dataAlign(HorizontalAlign.LEFT).with(r -> r.message)))); + } + + return 0; + } + + private static void fetchEvents(JsonObject root, Row row, String type, List<Row> rows) { + JsonObject jo = (JsonObject) root.get("events"); + if (jo != null) { + JsonArray arr = (JsonArray) jo.get(type); + if (arr != null) { + for (Object o : arr) { + row = row.copy(); + jo = (JsonObject) o; + row.type = jo.getString("type"); + Long ts = jo.getLong("timestamp"); + if (ts != null) { + row.timestamp = ts; + } + row.exchangeId = jo.getString("exchangeId"); + row.message = jo.getString("message"); + rows.add(row); + } + } + } + } + + private String getTimestamp(Row r) { + if (r.timestamp > 0) { + return TimeUtils.printSince(r.timestamp); + } + return ""; + } + + protected int sortRow(Row o1, Row o2) { + String s = sort; + int negate = 1; + if (s.startsWith("-")) { + s = s.substring(1); + negate = -1; + } + switch (s) { + case "pid": + return Long.compare(Long.parseLong(o1.pid), Long.parseLong(o2.pid)) * negate; + case "name": + return o1.name.compareToIgnoreCase(o2.name) * negate; + case "age": + return Long.compare(o1.uptime, o2.uptime) * negate; + default: + return 0; + } + } + + private static class Row implements Cloneable { + String pid; + String name; + String age; + long uptime; + String type; + long timestamp; + String exchangeId; + String message; + + Row copy() { + try { + return (Row) clone(); + } catch (CloneNotSupportedException e) { + return null; + } + } + } + +}