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");