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 dcbf8099711c CAMEL-26929: camel-jbang - camel cmd receive --since 
filters, --output=json has only JSON, and an endpoint that matches nothing is 
not found (#29936)
dcbf8099711c is described below

commit dcbf8099711c7203b61aab9dae703e121143ad4c
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Nov 14 19:29:06 2026 +0100

    CAMEL-26929: camel-jbang - camel cmd receive --since filters, --output=json 
has only JSON, and an endpoint that matches nothing is not found (#29936)
    
    The receive console took the message timestamp, which is 0 for most 
components, so --since
    never filtered: it falls back to the time the exchange was created. 
--action=status
    --output=json printed the table, the JSON stream started with 'Starting to 
receive ...' on
    stdout, --action=stop said 'Starting to receive', and a start with an 
endpoint that matched
    nothing exited 0.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../camel/impl/console/ReceiveDevConsole.java      |  4 ++
 .../camel/impl/console/ReceiveDevConsoleTest.java  | 21 ++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  4 ++
 .../core/commands/action/CamelReceiveAction.java   | 49 +++++++++++++++++--
 .../commands/action/CamelReceiveActionTest.java    | 56 +++++++++++++++++++++-
 5 files changed, 128 insertions(+), 6 deletions(-)

diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ReceiveDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ReceiveDevConsole.java
index 97a406aac315..42f98a8fa401 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/ReceiveDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/ReceiveDevConsole.java
@@ -293,6 +293,10 @@ public class ReceiveDevConsole extends AbstractDevConsole {
         json.put("endpointUri", exchange.getFromEndpoint().toString());
         json.put("remoteEndpoint", exchange.getFromEndpoint().isRemote());
         lastTimestamp = exchange.getMessage().getMessageTimestamp();
+        if (lastTimestamp == 0) {
+            // most components set no message timestamp, and camel cmd receive 
--since filters by it (CAMEL-26929)
+            lastTimestamp = exchange.getClock().getCreated();
+        }
         json.put("timestamp", lastTimestamp);
 
         ConsoleHelper.offerLast(queue, json);
diff --git 
a/core/camel-console/src/test/java/org/apache/camel/impl/console/ReceiveDevConsoleTest.java
 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/ReceiveDevConsoleTest.java
index e869996ea626..b6f2ac110f67 100644
--- 
a/core/camel-console/src/test/java/org/apache/camel/impl/console/ReceiveDevConsoleTest.java
+++ 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/ReceiveDevConsoleTest.java
@@ -17,6 +17,7 @@
 package org.apache.camel.impl.console;
 
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.camel.console.DevConsole;
@@ -61,4 +62,24 @@ public class ReceiveDevConsoleTest extends 
AbstractDevConsoleTest {
         assertNotNull(messages);
         assertTrue(messages.isEmpty());
     }
+
+    @Test
+    public void testAMessageWithoutTimestampHasTheTimeItWasReceived() {
+        // camel cmd receive --since filters by the timestamp, which was 0 for 
most components (CAMEL-26929)
+        DevConsole console = assertConsoleExists("receive", "camel");
+        ServiceHelper.startService(console);
+        JsonObject started = callJson(console, Map.of("endpoint", 
"direct:in"));
+        assertEquals("direct://in", started.getString("url"), 
started.toJson());
+
+        long before = System.currentTimeMillis();
+        
context.createFluentProducerTemplate().to("direct:in").withBody("Hello").send();
+        long after = System.currentTimeMillis();
+
+        JsonObject dump = callJson(console, Map.of("dump", "true"));
+        List<Map<String, Object>> messages = dump.getCollection("messages");
+        assertEquals(1, messages.size(), dump.toJson());
+        long timestamp = ((Number) 
messages.get(0).get("timestamp")).longValue();
+        assertTrue(timestamp >= before && timestamp <= after, "the time it was 
received: " + timestamp);
+        callJson(console, Map.of("enabled", "false"));
+    }
 }
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 663441224063..11304a49a80c 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -2803,6 +2803,10 @@ A script that checks the exit code of a command must 
take these changes into acc
 * A name that does not exist exits with `3` instead of `1`: the Kamelet of 
`camel init --from-kamelet`, the service of
   `camel infra run`, the key of `camel config get`, the name given to `camel 
doc`, and the plugin of
   `camel plugin delete`.
+* `camel cmd receive --action=start --endpoint=<pattern>` exits with `3`, "No 
endpoint matching", when the pattern
+  matches no endpoint of the integration, instead of `0`. With 
`--output=json`, the line "Starting to receive messages
+  ..." is printed on stderr, so stdout has only the JSON, and `--action=status 
--output=json` prints JSON instead of the
+  table.
 * `camel cmd memory-leak` and `camel restart` exit with `3` when no running 
integration, or more than one, matches the
   name. `camel cmd memory-leak` exits with `124` when the integration does not 
reply in time.
 * `camel init` without a file, or with `--from-kamelet` and a file that is not 
a `.kamelet.yaml` file,
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelReceiveAction.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelReceiveAction.java
index d493a0b89cfd..b40eff18c6e5 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelReceiveAction.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelReceiveAction.java
@@ -58,6 +58,7 @@ 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.CompletionCandidates;
 import org.apache.camel.dsl.jbang.core.common.CopyableRow;
+import org.apache.camel.dsl.jbang.core.common.JsonOutput;
 import org.apache.camel.dsl.jbang.core.common.ListingFormat;
 import org.apache.camel.dsl.jbang.core.common.PidNameAgeCompletionCandidates;
 import org.apache.camel.dsl.jbang.core.common.ProcessFinder;
@@ -288,8 +289,8 @@ public class CamelReceiveAction extends ActionBaseCommand {
                     Files.writeString(f, "");
                 }
             } else {
-                printer().println("Starting to receive messages from existing 
Camel: " + process.name() + " (pid: "
-                                  + process.pid() + ")");
+                info(("stop".equals(action) ? "Stopping" : "Starting") + " to 
receive messages from existing Camel: "
+                     + process.name() + " (pid: " + process.pid() + ")");
                 int code = showStatus(ActionClient.execute(process, "receive", 
this::receiveOptions, timeout + 10000));
                 if (exit == 0) {
                     exit = code;
@@ -352,8 +353,7 @@ public class CamelReceiveAction extends ActionBaseCommand {
         };
         // keep thread running as we need it to show the status before 
terminating
         t.start();
-        printer().println(
-                "Starting to receive messages by connecting to external system 
using local process (pid: " + pid + ")");
+        info("Starting to receive messages by connecting to external system 
using local process (pid: " + pid + ")");
 
         Integer exit = run.call();
         latch.await();
@@ -413,9 +413,26 @@ public class CamelReceiveAction extends ActionBaseCommand {
             }
             return CliExitCode.FAILURE;
         }
+        if ("start".equals(action) && endpoint != null && 
!"*".equals(endpoint) && jo != null
+                && jo.getString("url") == null) {
+            // the integration says which endpoint it receives from: it 
started to receive from none (CAMEL-26929)
+            printer().printErr("No endpoint matching: " + endpoint);
+            return CliExitCode.NOT_FOUND;
+        }
         return CliExitCode.OK;
     }
 
+    /**
+     * A line that says what the command does: not on stdout when the output 
is JSON, which then has only the JSON.
+     */
+    private void info(String line) {
+        if ("json".equalsIgnoreCase(output)) {
+            printer().printDiagnostic(line);
+        } else {
+            printer().println(line);
+        }
+    }
+
     protected Integer doStatusCall() {
         List<StatusRow> rows = new ArrayList<>();
 
@@ -469,7 +486,10 @@ public class CamelReceiveAction extends ActionBaseCommand {
 
         rows.sort(ORDER.comparator(sort));
 
-        if (!rows.isEmpty()) {
+        if ("json".equalsIgnoreCase(output)) {
+            // --output=json printed the table (CAMEL-26929)
+            JsonOutput.printList(printer(), 
rows.stream().map(CamelReceiveAction::statusAsJson).toList());
+        } else if (!rows.isEmpty()) {
             int tw = terminalWidth();
             int fixedWidth = 10 + 30 + 10 + 10 + 8 + 10; // PID + NAME + AGE + 
STATUS + TOTAL + SINCE (approx)
             int borderOverhead = TerminalWidthHelper.noBorderOverhead(7);
@@ -495,6 +515,25 @@ public class CamelReceiveAction extends ActionBaseCommand {
         return 0;
     }
 
+    private static JsonObject statusAsJson(StatusRow r) {
+        JsonObject jo = new JsonObject();
+        jo.put("pid", Long.parseLong(r.pid));
+        jo.put("name", r.name);
+        jo.put("uptime", r.uptime);
+        jo.put("enabled", r.enabled);
+        jo.put("total", r.counter);
+        if (r.firstTimestamp > 0) {
+            jo.put("firstTimestamp", r.firstTimestamp);
+        }
+        if (r.lastTimestamp > 0) {
+            jo.put("lastTimestamp", r.lastTimestamp);
+        }
+        if (r.uri != null) {
+            jo.put("endpoint", r.uri);
+        }
+        return jo;
+    }
+
     private String getStatus(StatusRow r) {
         if (r.enabled) {
             return "Enabled";
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelReceiveActionTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelReceiveActionTest.java
index 6a94828f2475..eab520401f5f 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelReceiveActionTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelReceiveActionTest.java
@@ -81,6 +81,57 @@ class CamelReceiveActionTest extends 
ActionCommandTestSupport {
         assertThat(callWithResponse(command("myApp", "--action=stop"), new 
JsonObject())).isZero();
 
         
assertThat(readActionFile(TEST_PID).getString("enabled")).isEqualTo("false");
+        // it said "Starting to receive" (CAMEL-26929)
+        assertThat(printer.getOutput()).contains("Stopping to receive messages 
from existing Camel: myApp")
+                .doesNotContain("Starting");
+    }
+
+    @Test
+    void anEndpointThatMatchesNothingIsNotFound() throws Exception {
+        // the integration started to receive from no endpoint, and the 
command exited 0 (CAMEL-26929)
+        writeStatusFile(TEST_PID, "myApp");
+
+        assertThat(callWithResponse(command("myApp", "--action=start", 
"--endpoint=nope*"), new JsonObject()))
+                .isEqualTo(CliExitCode.NOT_FOUND);
+
+        assertThat(printer.getStderr()).contains("No endpoint matching: 
nope*");
+        assertThat(printer.getOutput()).doesNotContain("Received Message");
+    }
+
+    @Test
+    void theJsonOfStartHasNoTextOnStdout() throws Exception {
+        // the JSON stream started with "Starting to receive ..." (CAMEL-26929)
+        writeStatusFile(TEST_PID, "myApp");
+        writeReceived(batch(message(1, "Hello Camel")));
+        JsonObject reply = new JsonObject();
+        reply.put("url", "seda://orders");
+
+        assertThat(callWithResponse(command("myApp", "--endpoint=seda:orders", 
"--output=json"), reply)).isZero();
+
+        assertThat(printer.getStdout()).doesNotContain("Starting to receive");
+        assertThat(printer.getStderr()).contains("Starting to receive messages 
from existing Camel: myApp");
+        for (String line : printer.getStdout().split("\\R")) {
+            if (!line.isBlank()) {
+                
assertThat(Jsoner.deserialize(line)).as(line).isInstanceOf(JsonObject.class);
+            }
+        }
+    }
+
+    @Test
+    void statusAsJson() throws Exception {
+        // --output=json printed the table (CAMEL-26929)
+        writeStatusFile(TEST_PID, receiveStatus("seda:orders", 
"jms:payments?password=secret"));
+
+        assertThat(callWithSingleProcess(command("myApp", "--output=json", 
"--mask"))).isZero();
+
+        JsonArray rows = (JsonArray) Jsoner.deserialize(printer.getStdout());
+        assertThat(rows).hasSize(2);
+        JsonObject row = (JsonObject) rows.get(0);
+        assertThat(row.getLong("pid")).isEqualTo(TEST_PID);
+        assertThat(row.getString("name")).isEqualTo("myApp");
+        assertThat(row.getBoolean("enabled")).isTrue();
+        assertThat(rows.stream().map(r -> ((JsonObject) 
r).getString("endpoint")).toList())
+                .contains("seda:orders").noneMatch(u -> u.contains("secret"));
     }
 
     @Test
@@ -101,7 +152,10 @@ class CamelReceiveActionTest extends 
ActionCommandTestSupport {
         writeStatusFile(TEST_PID, "myApp");
         writeReceived(batch(message(1, "Hello Camel")));
 
-        assertThat(callWithResponse(command("myApp", 
"--endpoint=seda:orders"), new JsonObject())).isZero();
+        // the integration says which endpoint it receives from
+        JsonObject reply = new JsonObject();
+        reply.put("url", "seda://orders");
+        assertThat(callWithResponse(command("myApp", 
"--endpoint=seda:orders"), reply)).isZero();
 
         
assertThat(readActionFile(TEST_PID).getString("endpoint")).isEqualTo("seda:orders");
         assertThat(printer.getOutput()).contains("Received Message: (1)", 
"Hello Camel");

Reply via email to