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 83308c290ae7 CAMEL-22629: camel-console - Add producer dev console, 
and jbang command. (#19792)
83308c290ae7 is described below

commit 83308c290ae7b0d56e4b2f9757e48e756a796dd7
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 3 12:33:58 2025 +0100

    CAMEL-22629: camel-console - Add producer dev console, and jbang command. 
(#19792)
---
 .../apache/camel/catalog/dev-consoles.properties   |   1 +
 .../camel/catalog/dev-consoles/producer.json       |  15 ++
 .../org/apache/camel/dev-console/producer.json     |  15 ++
 .../services/org/apache/camel/dev-console/producer |   2 +
 .../org/apache/camel/dev-consoles.properties       |   2 +-
 .../camel/impl/console/ProducerDevConsole.java     | 119 ++++++++++++
 .../camel/cli/connector/LocalCliConnector.java     |   7 +
 .../dsl/jbang/core/commands/CamelJBangMain.java    |   1 +
 .../jbang/core/commands/process/ListProducer.java  | 212 +++++++++++++++++++++
 9 files changed, 373 insertions(+), 1 deletion(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties
index 7f97cf277718..9b787ecb27df 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties
@@ -31,6 +31,7 @@ memory
 micrometer
 platform-http
 processor
+producer
 properties
 quartz
 receive
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/producer.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/producer.json
new file mode 100644
index 000000000000..957fd297a24e
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/producer.json
@@ -0,0 +1,15 @@
+{
+  "console": {
+    "kind": "console",
+    "group": "camel",
+    "name": "producer",
+    "title": "Producers",
+    "description": "Display information about Camel producers",
+    "deprecated": false,
+    "javaType": "org.apache.camel.impl.console.ProducerDevConsole",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-console",
+    "version": "4.16.0-SNAPSHOT"
+  }
+}
+
diff --git 
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/producer.json
 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/producer.json
new file mode 100644
index 000000000000..957fd297a24e
--- /dev/null
+++ 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/producer.json
@@ -0,0 +1,15 @@
+{
+  "console": {
+    "kind": "console",
+    "group": "camel",
+    "name": "producer",
+    "title": "Producers",
+    "description": "Display information about Camel producers",
+    "deprecated": false,
+    "javaType": "org.apache.camel.impl.console.ProducerDevConsole",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-console",
+    "version": "4.16.0-SNAPSHOT"
+  }
+}
+
diff --git 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/producer
 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/producer
new file mode 100644
index 000000000000..3e7926c16fc5
--- /dev/null
+++ 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/producer
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.impl.console.ProducerDevConsole
diff --git 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
index af617cb7354a..0591dd83a003 100644
--- 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
+++ 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
@@ -1,5 +1,5 @@
 # Generated by camel build tools - do NOT edit this file!
-dev-consoles=bean blocked browse circuit-breaker consumer context debug 
endpoint event gc health inflight internal-tasks java-security jvm log memory 
processor properties receive reload rest route route-controller route-dump 
route-group route-structure send service source startup-recorder 
system-properties thread top trace transformers type-converters variables
+dev-consoles=bean blocked browse circuit-breaker consumer context debug 
endpoint event gc health inflight internal-tasks java-security jvm log memory 
processor producer properties receive reload rest route route-controller 
route-dump route-group route-structure send service source startup-recorder 
system-properties thread top trace transformers type-converters variables
 groupId=org.apache.camel
 artifactId=camel-console
 version=4.16.0-SNAPSHOT
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ProducerDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ProducerDevConsole.java
new file mode 100644
index 000000000000..b8c9d96da367
--- /dev/null
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ProducerDevConsole.java
@@ -0,0 +1,119 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.api.management.mbean.ManagedProducerMBean;
+import org.apache.camel.spi.annotations.DevConsole;
+import org.apache.camel.support.console.AbstractDevConsole;
+import org.apache.camel.util.json.JsonObject;
+
+@DevConsole(name = "producer", displayName = "Producers", description = 
"Display information about Camel producers")
+public class ProducerDevConsole extends AbstractDevConsole {
+
+    public ProducerDevConsole() {
+        super("camel", "producer", "Producers", "Display information about 
Camel producers");
+    }
+
+    @Override
+    protected String doCallText(Map<String, Object> options) {
+        StringBuilder sb = new StringBuilder();
+
+        MBeanServer mbeanServer = 
getCamelContext().getManagementStrategy().getManagementAgent().getMBeanServer();
+        if (mbeanServer != null) {
+            try {
+                String jmxDomain
+                        = 
getCamelContext().getManagementStrategy().getManagementAgent().getMBeanObjectDomainName();
+                String prefix
+                        = 
getCamelContext().getManagementStrategy().getManagementAgent().getIncludeHostName()
 ? "*/" : "";
+                ObjectName query = ObjectName.getInstance(
+                        jmxDomain + ":context=" + prefix + 
getCamelContext().getManagementName() + ",type=producers,*");
+                Set<ObjectName> set = mbeanServer.queryNames(query, null);
+                if (set != null && !set.isEmpty()) {
+                    for (ObjectName on : set) {
+                        ManagedProducerMBean mp = 
getManagedProducer(getCamelContext(), on);
+                        if (!sb.isEmpty()) {
+                            sb.append("\n");
+                        }
+                        sb.append(String.format("\n    Uri: %s", 
mp.getEndpointUri()));
+                        sb.append(String.format("\n    State: %s", 
mp.getState()));
+                        sb.append(String.format("\n    Class: %s", 
mp.getServiceType()));
+                        sb.append(String.format("\n    Remote: %b", 
mp.isRemoteEndpoint()));
+                        sb.append(String.format("\n    Singleton: %b", 
mp.isSingleton()));
+                        if (mp.getRouteId() != null) {
+                            sb.append(String.format("\n    Route Id: %s", 
mp.getRouteId()));
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+        return sb.toString();
+    }
+
+    @Override
+    protected JsonObject doCallJson(Map<String, Object> options) {
+        final JsonObject root = new JsonObject();
+        final List<JsonObject> list = new ArrayList<>();
+        root.put("producers", list);
+
+        MBeanServer mbeanServer = 
getCamelContext().getManagementStrategy().getManagementAgent().getMBeanServer();
+        if (mbeanServer != null) {
+            try {
+                String jmxDomain
+                        = 
getCamelContext().getManagementStrategy().getManagementAgent().getMBeanObjectDomainName();
+                String prefix
+                        = 
getCamelContext().getManagementStrategy().getManagementAgent().getIncludeHostName()
 ? "*/" : "";
+                ObjectName query = ObjectName.getInstance(
+                        jmxDomain + ":context=" + prefix + 
getCamelContext().getManagementName() + ",type=producers,*");
+                Set<ObjectName> set = mbeanServer.queryNames(query, null);
+                if (set != null && !set.isEmpty()) {
+                    for (ObjectName on : set) {
+                        ManagedProducerMBean mp = 
getManagedProducer(getCamelContext(), on);
+                        JsonObject jo = new JsonObject();
+                        jo.put("uri", mp.getEndpointUri());
+                        jo.put("state", mp.getState());
+                        jo.put("class", mp.getServiceType());
+                        jo.put("remote", mp.isRemoteEndpoint());
+                        jo.put("singleton", mp.isSingleton());
+                        if (mp.getRouteId() != null) {
+                            jo.put("routeId", mp.getRouteId());
+                        }
+                        list.add(jo);
+                    }
+                }
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+        return root;
+    }
+
+    private static ManagedProducerMBean getManagedProducer(CamelContext 
camelContext, ObjectName on) {
+        return 
camelContext.getManagementStrategy().getManagementAgent().newProxyClient(on, 
ManagedProducerMBean.class);
+    }
+
+}
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 907dd7ae94f2..dd1e43add1f2 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
@@ -1098,6 +1098,13 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
                         root.put("consumers", json);
                     }
                 }
+                DevConsole dc14b = dcr.resolveById("producer");
+                if (dc14b != null) {
+                    JsonObject json = (JsonObject) 
dc14b.call(DevConsole.MediaType.JSON);
+                    if (json != null && !json.isEmpty()) {
+                        root.put("producers", json);
+                    }
+                }
                 DevConsole dc15 = dcr.resolveById("variables");
                 if (dc15 != null) {
                     JsonObject json = (JsonObject) 
dc15.call(DevConsole.MediaType.JSON);
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 994e1368c79c..2c9e43633764 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
@@ -105,6 +105,7 @@ public class CamelJBangMain implements Callable<Integer> {
                         .addSubcommand("properties", new CommandLine(new 
ListProperties(main)))
                         .addSubcommand("variable", new CommandLine(new 
ListVariable(main)))
                         .addSubcommand("consumer", new CommandLine(new 
ListConsumer(main)))
+                        .addSubcommand("producer", new CommandLine(new 
ListProducer(main)))
                         .addSubcommand("endpoint", new CommandLine(new 
ListEndpoint(main)))
                         .addSubcommand("event", new CommandLine(new 
ListEvent(main)))
                         .addSubcommand("inflight", new CommandLine(new 
ListInflight(main)))
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListProducer.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListProducer.java
new file mode 100644
index 000000000000..c19723fe8929
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListProducer.java
@@ -0,0 +1,212 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+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.PidNameAgeCompletionCandidates;
+import org.apache.camel.dsl.jbang.core.common.ProcessHelper;
+import org.apache.camel.support.PatternHelper;
+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;
+
+@Command(name = "producer", description = "Get status of Camel producers", 
sortOptions = false, showDefaultValues = true)
+public class ListProducer extends ProcessWatchCommand {
+
+    @CommandLine.Parameters(description = "Name or pid of running Camel 
integration", arity = "0..1")
+    String name = "*";
+
+    @CommandLine.Option(names = { "--sort" }, completionCandidates = 
PidNameAgeCompletionCandidates.class,
+                        description = "Sort by pid, name or age", defaultValue 
= "pid")
+    String sort;
+
+    @CommandLine.Option(names = { "--limit" },
+                        description = "Filter producers by limiting to the 
given number of rows")
+    int limit;
+
+    @CommandLine.Option(names = { "--filter" },
+                        description = "Filter producers by URI")
+    String filter;
+
+    @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;
+
+    public ListProducer(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doProcessWatchCall() throws Exception {
+        List<Row> rows = new ArrayList<>();
+
+        // make it easier to filter
+        if (filter != null && !filter.endsWith("*")) {
+            filter += "*";
+        }
+
+        List<Long> pids = findPids(name);
+        ProcessHandle.allProcesses()
+                .filter(ph -> pids.contains(ph.pid()))
+                .forEach(ph -> {
+                    JsonObject root = loadStatus(ph.pid());
+                    if (root != null) {
+                        JsonObject context = (JsonObject) root.get("context");
+                        JsonObject jo = (JsonObject) root.get("producers");
+                        if (context != null && jo != null) {
+                            JsonArray array = (JsonArray) jo.get("producers");
+                            for (int i = 0; i < array.size(); i++) {
+                                JsonObject o = (JsonObject) array.get(i);
+                                Row row = new Row();
+                                row.name = context.getString("name");
+                                if ("CamelJBang".equals(row.name)) {
+                                    row.name = ProcessHelper.extractName(root, 
ph);
+                                }
+                                row.pid = Long.toString(ph.pid());
+                                row.uri = o.getString("uri");
+                                row.id = o.getString("routeId");
+                                row.state = o.getString("state");
+                                row.className = o.getString("class");
+                                row.singleton = o.getBoolean("singleton");
+                                row.remote = o.getBoolean("remote");
+                                row.uptime = extractSince(ph);
+                                row.age = TimeUtils.printSince(row.uptime);
+                                boolean add = true;
+                                if (filter != null) {
+                                    String f = filter;
+                                    boolean negate = filter.startsWith("-");
+                                    if (negate) {
+                                        f = f.substring(1);
+                                    }
+                                    boolean match = 
PatternHelper.matchPattern(row.uri, f);
+                                    if (negate) {
+                                        match = !match;
+                                    }
+                                    if (!match) {
+                                        add = false;
+                                    }
+                                }
+                                if (limit > 0 && rows.size() >= limit) {
+                                    add = false;
+                                }
+                                if (add) {
+                                    rows.add(row);
+                                }
+                            }
+                        }
+                    }
+                });
+
+        // sort rows
+        rows.sort(this::sortRow);
+
+        if (!rows.isEmpty()) {
+            printTable(rows);
+        }
+
+        return 0;
+    }
+
+    protected void printTable(List<Row> 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(30, 
OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(r -> r.name),
+                new 
Column().header("AGE").headerAlign(HorizontalAlign.CENTER).with(r -> r.age),
+                new 
Column().header("ID").dataAlign(HorizontalAlign.LEFT).maxWidth(20, 
OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(r -> r.id),
+                new 
Column().header("STATUS").headerAlign(HorizontalAlign.CENTER).with(this::getState),
+                new 
Column().header("TYPE").dataAlign(HorizontalAlign.LEFT).maxWidth(20, 
OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(this::getType),
+                new 
Column().header("URI").visible(!wideUri).dataAlign(HorizontalAlign.LEFT)
+                        .maxWidth(90, OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(this::getUri),
+                new 
Column().header("URI").visible(wideUri).dataAlign(HorizontalAlign.LEFT)
+                        .maxWidth(140, OverflowBehaviour.NEWLINE)
+                        .with(this::getUri))));
+    }
+
+    private String getUri(Row r) {
+        String u = r.uri;
+        if (shortUri) {
+            int pos = u.indexOf('?');
+            if (pos > 0) {
+                u = u.substring(0, pos);
+            }
+        }
+        return u;
+    }
+
+    private String getState(Row r) {
+        return r.state;
+    }
+
+    private String getType(Row r) {
+        String s = r.className;
+        if (s.endsWith("Producer")) {
+            s = s.substring(0, s.length() - 8);
+        }
+        return s;
+    }
+
+    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;
+        }
+    }
+
+    static class Row {
+        String pid;
+        String name;
+        long uptime;
+        String age;
+        String id;
+        String uri;
+        String state;
+        String className;
+        boolean singleton;
+        boolean remote;
+    }
+
+}

Reply via email to