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

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

commit 80a374a25b03559feed6aae966a2823f32edd6cf
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Sep 5 16:36:46 2024 +0200

    CAMEL-21178: Add browse jbang command
---
 .../dsl/jbang/core/commands/CamelJBangMain.java    |   1 +
 .../core/commands/action/ActionBaseCommand.java    |   2 +-
 .../core/commands/action/CamelBrowseAction.java    | 275 +++++++++++++++++++++
 .../core/commands/action/MessageTableHelper.java   |  22 +-
 4 files changed, 289 insertions(+), 11 deletions(-)

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 c7627b3897a..58d7b13e44f 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
@@ -122,6 +122,7 @@ public class CamelJBangMain implements Callable<Integer> {
                         .addSubcommand("reset-stats", new CommandLine(new 
CamelResetStatsAction(main)))
                         .addSubcommand("reload", new CommandLine(new 
CamelReloadAction(main)))
                         .addSubcommand("send", new CommandLine(new 
CamelSendAction(main)))
+                        .addSubcommand("browse", new CommandLine(new 
CamelBrowseAction(main)))
                         .addSubcommand("stub", new CommandLine(new 
CamelStubAction(main)))
                         .addSubcommand("thread-dump", new CommandLine(new 
CamelThreadDump(main)))
                         .addSubcommand("logger", new CommandLine(new 
LoggerAction(main)))
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionBaseCommand.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionBaseCommand.java
index 6ea515545b9..7520e89cd47 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionBaseCommand.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionBaseCommand.java
@@ -94,7 +94,7 @@ abstract class ActionBaseCommand extends CamelCommand {
 
     static long extractSince(ProcessHandle ph) {
         long since = 0;
-        if (ph.info().startInstant().isPresent()) {
+        if (ph != null && ph.info().startInstant().isPresent()) {
             since = ph.info().startInstant().get().toEpochMilli();
         }
         return since;
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelBrowseAction.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelBrowseAction.java
new file mode 100644
index 00000000000..177b53d2446
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelBrowseAction.java
@@ -0,0 +1,275 @@
+/*
+ * 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.action;
+
+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.dsl.jbang.core.common.ProcessHelper;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.TimeUtils;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import picocli.CommandLine;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
[email protected](name = "browse",
+                     description = "Browse pending messages on endpoints", 
sortOptions = false)
+public class CamelBrowseAction extends ActionBaseCommand {
+
+    public static class UriSizeCompletionCandidates implements 
Iterable<String> {
+
+        public UriSizeCompletionCandidates() {
+        }
+
+        @Override
+        public Iterator<String> iterator() {
+            return List.of("uri", "size").iterator();
+        }
+
+    }
+
+    @CommandLine.Parameters(description = "Name or pid of running Camel 
integration", arity = "0..1")
+    String name = "*";
+
+    @CommandLine.Option(names = { "--endpoint" },
+                        description = "Endpoint to browse messages (can be 
uri, pattern, or refer to a route id)")
+    String endpoint;
+
+    @CommandLine.Option(names = { "--short-uri" },
+            description = "List endpoint URI without query parameters (short)")
+    boolean shortUri;
+
+    @CommandLine.Option(names = { "--wide-uri" },
+            description = "List endpoint URI in full details")
+    boolean wideUri;
+
+    @CommandLine.Option(names = { "--limit" }, defaultValue = "100",
+            description = "Limits the number of messages to dump per endpoint")
+    int limit;
+
+    @CommandLine.Option(names = { "--dump" }, defaultValue = "false",
+            description = "Whether to include message dumps")
+    boolean dump;
+
+    @CommandLine.Option(names = { "--sort" }, completionCandidates = 
UriSizeCompletionCandidates.class,
+            description = "Sort by uri, or size", defaultValue = "uri")
+    String sort;
+
+    @CommandLine.Option(names = { "--show-exchange-properties" }, defaultValue 
= "false",
+                        description = "Show exchange properties in browsed 
messages")
+    boolean showExchangeProperties;
+
+    @CommandLine.Option(names = { "--show-headers" }, defaultValue = "true",
+                        description = "Show message headers in browsed 
messages")
+    boolean showHeaders = true;
+
+    @CommandLine.Option(names = { "--show-body" }, defaultValue = "true",
+                        description = "Show message body in browsed messages")
+    boolean showBody = true;
+
+    @CommandLine.Option(names = { "--logging-color" }, defaultValue = "true", 
description = "Use colored logging")
+    boolean loggingColor = true;
+
+    @CommandLine.Option(names = { "--pretty" },
+                        description = "Pretty print message body when using 
JSon or XML format")
+    boolean pretty;
+
+    private volatile long pid;
+
+    private MessageTableHelper tableHelper;
+
+    public CamelBrowseAction(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doCall() throws Exception {
+        List<Long> pids = findPids(name);
+        if (pids.isEmpty()) {
+            return 0;
+        } else if (pids.size() > 1) {
+            printer().println("Name or pid " + name + " matches " + pids.size()
+                              + " running Camel integrations. Specify a name 
or PID that matches exactly one.");
+            return 0;
+        }
+
+        this.pid = pids.get(0);
+
+        // ensure output file is deleted before executing action
+        File outputFile = getOutputFile(Long.toString(pid));
+        FileUtil.deleteFile(outputFile);
+
+        JsonObject root = new JsonObject();
+        root.put("action", "browse");
+        root.put("filter", endpoint == null ? "*" : endpoint);
+        root.put("limit", limit);
+        root.put("dump", dump);
+
+        File f = getActionFile(Long.toString(pid));
+        try {
+            IOHelper.writeText(root.toJson(), f);
+        } catch (Exception e) {
+            // ignore
+        }
+
+        List<BrowseRow> rows = new ArrayList<>();
+        JsonObject jo = getJsonObject(outputFile);
+        if (jo != null) {
+            root = loadStatus(this.pid);
+            if (root != null) {
+                BrowseRow row = new BrowseRow();
+                row.pid = Long.toString(this.pid);
+                JsonObject context = (JsonObject) root.get("context");
+                if (context == null) {
+                    return 0;
+                }
+                ProcessHandle ph = ProcessHandle.of(this.pid).orElse(null);
+                row.name = context.getString("name");
+                if ("CamelJBang".equals(row.name)) {
+                    row.name = ProcessHelper.extractName(root, ph);
+                }
+                row.uptime = extractSince(ph);
+                row.ago = TimeUtils.printSince(row.uptime);
+                JsonArray arr = jo.getCollection("browse");
+                for (int i = 0; arr != null && i < arr.size(); i++) {
+                    JsonObject o = (JsonObject) arr.get(i);
+                    row.uri = o.getString("endpointUri");
+                    row.size = o.getLong("size");
+                    if (dump) {
+                        row.messages = o.getCollection("messages");
+                    }
+                    rows.add(row);
+                    row = row.copy();
+                }
+            }
+        }
+
+        // sort rows
+        rows.sort(this::sortRow);
+
+        if (dump) {
+            dumpMessages(rows);
+        } else {
+            tableStatus(rows);
+        }
+
+        // delete output file after use
+        FileUtil.deleteFile(outputFile);
+
+        return 0;
+    }
+
+    protected void dumpMessages(List<BrowseRow> rows) {
+        tableHelper = new MessageTableHelper();
+        tableHelper.setPretty(pretty);
+        tableHelper.setLoggingColor(loggingColor);
+        tableHelper.setShowExchangeProperties(showExchangeProperties);
+
+        for (BrowseRow row : rows) {
+            if (row.messages != null) {
+                for (int i = 0; i < row.messages.size(); i++) {
+                    JsonObject jo = row.messages.get(i);
+
+                    String exchangeId = jo.getString("exchangeId");
+                    JsonObject message = jo.getMap("message");
+                    if (!showExchangeProperties && message != null) {
+                        message.remove("exchangeProperties");
+                    }
+                    if (!showHeaders && message != null) {
+                        message.remove("headers");
+                    }
+                    if (!showBody && message != null) {
+                        message.remove("body");
+                    }
+                    JsonObject ep = new JsonObject();
+                    ep.put("endpoint", row.uri);
+                    String table = tableHelper.getDataAsTable(exchangeId, 
null, ep, null, message, null);
+                    printer().println(table);
+                }
+            }
+        }
+    }
+
+    protected void tableStatus(List<BrowseRow> rows) {
+        printer().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(40, OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(r -> r.name),
+                new 
Column().header("AGE").headerAlign(HorizontalAlign.CENTER).with(r -> r.ago),
+                new 
Column().header("ENDPOINT").visible(!wideUri).dataAlign(HorizontalAlign.LEFT)
+                        .maxWidth(45, OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(this::getEndpointUri),
+                new Column().header("SIZE").with(r -> "" + r.size))));
+    }
+
+    protected int sortRow(BrowseRow o1, BrowseRow o2) {
+        String s = sort;
+        int negate = 1;
+        if (s.startsWith("-")) {
+            s = s.substring(1);
+            negate = -1;
+        }
+        switch (s) {
+            case "uri":
+                return o1.uri.compareToIgnoreCase(o2.uri) * negate;
+            case "size":
+                return Long.compare(o1.size, o2.size) * negate;
+            default:
+                return 0;
+        }
+    }
+
+    protected String getEndpointUri(BrowseRow r) {
+        String u = r.uri;
+        if (shortUri) {
+            int pos = u.indexOf('?');
+            if (pos > 0) {
+                u = u.substring(0, pos);
+            }
+        }
+        return u;
+    }
+
+    private static class BrowseRow implements Cloneable {
+        String pid;
+        String name;
+        String ago;
+        long uptime;
+        String uri;
+        long size;
+        List<JsonObject> messages;
+
+        BrowseRow copy() {
+            try {
+                return (BrowseRow) clone();
+            } catch (CloneNotSupportedException e) {
+                return null;
+            }
+        }
+    }
+
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelper.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelper.java
index b7fdc7cadeb..cd9d384ceff 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/MessageTableHelper.java
@@ -137,16 +137,18 @@ public class MessageTableHelper {
         }
 
         if (root != null) {
-            eRow = new TableRow("Exchange", root.getString("exchangeType"), 
exchangePattern, exchangeId);
-            tab1 = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(eRow), 
Arrays.asList(
-                    new Column().dataAlign(HorizontalAlign.LEFT)
-                            .minWidth(showExchangeProperties || 
showExchangeVariables ? 12 : 10).with(TableRow::kindAsString),
-                    new 
Column().dataAlign(HorizontalAlign.LEFT).with(TableRow::typeAsString)));
-            tab1b = AsciiTable.getTable(AsciiTable.NO_BORDERS, List.of(eRow), 
Arrays.asList(
-                    new Column().dataAlign(HorizontalAlign.CENTER)
-                            
.minWidth(18).maxWidth(18).with(TableRow::mepAsKey),
-                    new Column().dataAlign(HorizontalAlign.RIGHT)
-                            .maxWidth(80).with(TableRow::exchangeIdAsValue)));
+            if (exchangeId != null) {
+                eRow = new TableRow("Exchange", 
root.getString("exchangeType"), exchangePattern, exchangeId);
+                tab1 = AsciiTable.getTable(AsciiTable.NO_BORDERS, 
List.of(eRow), Arrays.asList(
+                        new Column().dataAlign(HorizontalAlign.LEFT)
+                                .minWidth(showExchangeProperties || 
showExchangeVariables ? 12 : 10).with(TableRow::kindAsString),
+                        new 
Column().dataAlign(HorizontalAlign.LEFT).with(TableRow::typeAsString)));
+                tab1b = AsciiTable.getTable(AsciiTable.NO_BORDERS, 
List.of(eRow), Arrays.asList(
+                        new Column().dataAlign(HorizontalAlign.CENTER)
+                                
.minWidth(18).maxWidth(18).with(TableRow::mepAsKey),
+                        new Column().dataAlign(HorizontalAlign.RIGHT)
+                                
.maxWidth(80).with(TableRow::exchangeIdAsValue)));
+            }
             // exchange variables
             JsonArray arr = root.getCollection("exchangeVariables");
             if (arr != null) {

Reply via email to