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 b918198190c5 CAMEL-26929: camel-jbang - camel cmd send without a name 
sends to the integration that runs (#29914)
b918198190c5 is described below

commit b918198190c5daba4944315ce67953920ce3b1fc
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Nov 14 19:23:13 2026 +0100

    CAMEL-26929: camel-jbang - camel cmd send without a name sends to the 
integration that runs (#29914)
    
    Without a name, camel cmd send sent from an empty Camel of the CLI: without
    --endpoint nothing was sent and it said "Sent (success)", and an endpoint 
that
    only an integration has (direct:, seda:, stub:, a route id) waited for the
    timeout. camel-jbang-tips recommends camel cmd send --body=... after camel 
run
    --stub=jms.
    
    Now such a send goes to the integration that runs (with none or several 
running
    it exits 3 and says what to give); an endpoint of a system outside the
    integrations, such as kafka:topic, is still sent from the CLI. The send 
console
    reports a send without an endpoint to a Camel without routes as an error.
    
    CAMEL-26929: upgrade guide - shorter sentences in the camel cmd send entry
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../apache/camel/impl/console/SendDevConsole.java  | 19 ++++--
 .../impl/console/SendDevConsoleNoRoutesTest.java   | 46 ++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  9 +++
 .../modules/ROOT/pages/camel-jbang-devtools.adoc   |  8 ++-
 .../core/commands/action/CamelSendAction.java      | 48 +++++++++++++-
 .../core/commands/action/CamelSendActionTest.java  | 73 ++++++++++++++++++++++
 .../src/test/resources/golden/cli-help.txt         |  6 +-
 7 files changed, 200 insertions(+), 9 deletions(-)

diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/SendDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/SendDevConsole.java
index cc83ced9e0f1..0ae6c8e82ef5 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/SendDevConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/SendDevConsole.java
@@ -154,8 +154,8 @@ public class SendDevConsole extends AbstractDevConsole {
         } catch (Exception e) {
             cause = e;
         }
-        if (endpoint != null && target == null) {
-            cause = new NoSuchEndpointException(endpoint);
+        if (target == null) {
+            cause = noTarget(endpoint);
         }
         if (out != null && out.getException() != null) {
             cause = out.getException();
@@ -219,8 +219,8 @@ public class SendDevConsole extends AbstractDevConsole {
         } catch (Exception e) {
             cause = e;
         }
-        if (cause == null && endpoint != null && target == null) {
-            cause = new NoSuchEndpointException(endpoint);
+        if (cause == null && target == null) {
+            cause = noTarget(endpoint);
         }
         if (out != null && out.getException() != null) {
             cause = out.getException();
@@ -284,6 +284,17 @@ public class SendDevConsole extends AbstractDevConsole {
         return out;
     }
 
+    /**
+     * Why nothing was sent: no endpoint matches, or, without an endpoint, 
there is no route to send to (before, that
+     * was reported as sent).
+     */
+    private static Exception noTarget(String endpoint) {
+        if (endpoint != null) {
+            return new NoSuchEndpointException(endpoint);
+        }
+        return new IllegalArgumentException("No endpoint to send to: give the 
endpoint, as there are no routes");
+    }
+
     private Endpoint findTarget(String endpoint) {
         Endpoint target = null;
         if (endpoint == null) {
diff --git 
a/core/camel-console/src/test/java/org/apache/camel/impl/console/SendDevConsoleNoRoutesTest.java
 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/SendDevConsoleNoRoutesTest.java
new file mode 100644
index 000000000000..c280da3e82c9
--- /dev/null
+++ 
b/core/camel-console/src/test/java/org/apache/camel/impl/console/SendDevConsoleNoRoutesTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.HashMap;
+import java.util.Map;
+
+import org.apache.camel.console.DevConsole;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A send without an endpoint to a Camel without routes sends nothing, and 
says so (it was reported as sent).
+ */
+public class SendDevConsoleNoRoutesTest extends AbstractDevConsoleTest {
+
+    @Test
+    public void aSendWithoutEndpointAndRoutesIsAnError() {
+        DevConsole console = assertConsoleExists("send");
+        Map<String, Object> options = new HashMap<>();
+        options.put(SendDevConsole.BODY, "Hello");
+
+        JsonObject json = callJson(console, options);
+        assertEquals("error", json.getString("status"));
+        String text = callText(console, options);
+        assertTrue(text.contains("Status: error"), text);
+        assertTrue(text.contains("No endpoint to send to: give the endpoint, 
as there are no routes"), text);
+    }
+}
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 3e3659e01173..29d263a043d3 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
@@ -1479,6 +1479,15 @@ A failure of the fallback itself is now also caught by 
the `doCatch` (before, it
 
 To have those steps use the error handler of the route, move them out of the 
`doTry` block.
 
+=== camel-jbang - camel cmd send without a name sends to the integration that 
runs
+
+`camel cmd send` without an integration name started a Camel of its own, 
without routes, and sent from there. That is
+still so for an endpoint of a system outside the integrations (such as 
`kafka:topic`). Without `--endpoint`, the
+message went nowhere and was reported as sent. An endpoint that only an 
integration has (`direct:`, `seda:`, `stub:`,
+a route id) waited for the timeout. Now both go to the integration that runs. 
With none running, or several, the
+command exits with `3` and says what to give. A send without an endpoint to an 
integration without routes is now
+an error, rather than reported as sent.
+
 === camel-aws2-kinesis - each endpoint uses its own client
 
 Each `aws2-kinesis` endpoint now uses the Kinesis client of its own 
configuration (`region`, credentials,
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-devtools.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-devtools.adoc
index 019ef6c84562..b30d28b846fc 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-devtools.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-devtools.adoc
@@ -97,8 +97,8 @@ rules can be tried on it (see 
xref:next@components:others:chaos.adoc[camel-chaos
 
 *Available since Camel 4.12*
 
-Omit the integration name to start a temporary Camel instance and send 
directly.
-All configuration must be provided on the command line:
+To send to a system outside the integrations, such as a broker, omit the 
integration name: the CLI starts a
+temporary Camel instance and sends directly. All configuration must be 
provided on the command line:
 
 [source,bash]
 ----
@@ -112,6 +112,10 @@ For complex components (Kafka, AWS, etc.), a Kamelet is 
often simpler:
 $ camel cmd send --body=file:payload.json 
--uri='kamelet:mqtt-sink?brokerUrl=tcp://mybroker:1883&topic=temperature'
 ----
 
+Without a name, a message for an integration goes to the integration that 
runs: with no `--endpoint` (the first
+route), or an endpoint that only an integration has (`direct:`, `seda:`, 
`stub:`, a route id or pattern), such as
+after `camel run --stub=jms`. When several integrations run, give the name or 
pid of the one to send to.
+
 === Sending to infrastructure services
 
 *Available since Camel 4.18*
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSendAction.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSendAction.java
index 9dd35d79cc17..87b43b832621 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSendAction.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSendAction.java
@@ -27,7 +27,9 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -51,6 +53,7 @@ import 
org.apache.camel.dsl.jbang.core.common.ActionClient.ActionResult;
 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.CommandLineHelper;
+import org.apache.camel.dsl.jbang.core.common.ProcessFinder;
 import org.apache.camel.dsl.jbang.core.common.ProcessFinder.CamelProcess;
 import org.apache.camel.dsl.jbang.core.common.TimeoutConverter;
 import org.apache.camel.dsl.jbang.run.ConfiguredRun;
@@ -78,7 +81,9 @@ import org.jline.jansi.Ansi;
                            "  camel cmd send --endpoint=kafka:refunds 
--body=file:test/chaos/refunds-fails/" })
 public class CamelSendAction extends ActionBaseCommand {
 
-    @Argument(description = "To use an existing running Camel integration for 
sending the message (name or pid)")
+    @Argument(description = "The running Camel integration to send to (name or 
pid). Without it, the integration that"
+                            + " runs, unless --endpoint is a system outside 
the integrations, such as kafka:topic,"
+                            + " which the CLI sends to itself")
     String name;
 
     @Option(name = "properties",
@@ -210,11 +215,52 @@ public class CamelSendAction extends ActionBaseCommand {
             return doCallInfra(infra);
         } else if (name != null) {
             return doCall(name);
+        } else if (endpoint == null || isInsideAnIntegration(endpoint)) {
+            // the Camel of this command has no routes: camel cmd send 
--body=... is for the integration that runs
+            return doCall(String.valueOf(theRunningIntegration().pid()));
         } else {
+            // a system outside the integrations, such as kafka:topic, from 
the Camel of this command
             return doCallLocal();
         }
     }
 
+    /**
+     * The schemes of the endpoints that only an integration has: a send from 
the Camel of this command reaches nobody.
+     */
+    private static final Set<String> INTERNAL_SCHEMES
+            = Set.of("direct", "seda", "stub", "disruptor", "disruptor-vm", 
"mock", "dataset", "vm");
+
+    /** Whether the endpoint is a route id, a pattern or an endpoint in the 
memory of an integration. */
+    static boolean isInsideAnIntegration(String endpoint) {
+        int colon = endpoint.indexOf(':');
+        return colon < 0 || endpoint.endsWith("*")
+                || INTERNAL_SCHEMES.contains(endpoint.substring(0, 
colon).toLowerCase(Locale.ROOT));
+    }
+
+    /**
+     * The one integration that runs, for a send without a name.
+     *
+     * @throws CliException (exit code 3) when none runs, or several
+     */
+    private CamelProcess theRunningIntegration() {
+        List<CamelProcess> running = ProcessFinder.find(null);
+        if (running.size() == 1) {
+            return running.get(0);
+        }
+        if (running.isEmpty()) {
+            throw CliException.notFound(endpoint == null
+                    ? "No running Camel integration to send to: start one with 
camel run, or give --endpoint to send"
+                      + " to a system such as kafka:topic"
+                    : "No running Camel integration to send to: " + endpoint + 
" is in an integration, so start it"
+                      + " with camel run first");
+        }
+        String names = running.stream()
+                .map(p -> p.name() != null && !p.name().isEmpty() ? p.name() + 
" (pid " + p.pid() + ")" : "pid " + p.pid())
+                .collect(Collectors.joining(", "));
+        throw CliException.notFound(running.size() + " Camel integrations are 
running: " + names
+                                    + ". Give the name or pid of the one to 
send to.");
+    }
+
     /**
      * The options of the send action.
      */
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSendActionTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSendActionTest.java
index 670df18a15b6..dffe0c03eb2c 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSendActionTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelSendActionTest.java
@@ -25,7 +25,9 @@ import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.junit.jupiter.MockitoExtension;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -73,6 +75,77 @@ class CamelSendActionTest extends ActionCommandTestSupport {
         assertTrue(printer.getOutput().isEmpty(), "nothing should be printed, 
was: " + printer.getOutput());
     }
 
+    @Test
+    void withoutANameTheMessageGoesToTheIntegrationThatRuns() throws Exception 
{
+        // camel run myroute.java --stub=jms, then camel cmd send --body=... 
(camel-jbang-tips.adoc): before, an empty
+        // Camel of the CLI took the message, which sent nothing and reported 
success (CAMEL-26929)
+        writeStatusFile(TEST_PID, "myApp");
+
+        CamelSendAction command = new CamelSendAction(new 
CamelJBangMain().withPrinter(printer));
+        command.body = "Something here";
+        command.loggingColor = false;
+
+        assertEquals(0, callWithResponse(command, sentResponse()));
+
+        JsonObject action = readActionFile(TEST_PID);
+        assertNotNull(action, "the running integration should get the send");
+        assertEquals("Something here", action.getString("body"));
+        assertNull(action.get("endpoint"));
+    }
+
+    @Test
+    void withoutANameAnEndpointOfAnIntegrationGoesToTheIntegrationThatRuns() 
throws Exception {
+        writeStatusFile(TEST_PID, "myApp");
+
+        CamelSendAction command = new CamelSendAction(new 
CamelJBangMain().withPrinter(printer));
+        command.endpoint = "direct:orders";
+        command.loggingColor = false;
+
+        assertEquals(0, callWithResponse(command, sentResponse()));
+
+        assertEquals("direct:orders", 
readActionFile(TEST_PID).getString("endpoint"));
+    }
+
+    @Test
+    void withoutANameAndNoIntegrationRunningTheSendIsRefused() {
+        CamelSendAction command = new CamelSendAction(new 
CamelJBangMain().withPrinter(printer));
+        command.endpoint = "seda:orders";
+
+        CliException e = assertThrows(CliException.class, () -> 
callWithSingleProcess(command));
+
+        assertEquals(CliExitCode.NOT_FOUND, e.getExitCode());
+        assertEquals("No running Camel integration to send to: seda:orders is 
in an integration, so start it with"
+                     + " camel run first",
+                e.getMessage());
+    }
+
+    @Test
+    void withoutANameAndSeveralIntegrationsRunningTheSendIsRefused() throws 
Exception {
+        writeStatusFile(TEST_PID, "myApp");
+        writeStatusFile(TEST_PID + 1, "billing");
+
+        CamelSendAction command = new CamelSendAction(new 
CamelJBangMain().withPrinter(printer));
+
+        CliException e = assertThrows(CliException.class,
+                () -> callWithResponse(command, sentResponse(), TEST_PID + 1));
+
+        assertEquals(CliExitCode.NOT_FOUND, e.getExitCode());
+        assertTrue(e.getMessage().startsWith("2 Camel integrations are 
running: "), e.getMessage());
+        assertTrue(e.getMessage().endsWith(". Give the name or pid of the one 
to send to."), e.getMessage());
+        assertNull(readActionFile(TEST_PID), "nothing is sent");
+    }
+
+    @Test
+    void anEndpointOutsideTheIntegrationsIsSentFromTheCli() {
+        assertTrue(CamelSendAction.isInsideAnIntegration("direct:foo"));
+        assertTrue(CamelSendAction.isInsideAnIntegration("seda://foo"));
+        assertTrue(CamelSendAction.isInsideAnIntegration("STUB:jms:inbox"));
+        assertTrue(CamelSendAction.isInsideAnIntegration("myRoute"));
+        assertTrue(CamelSendAction.isInsideAnIntegration("kafka:*"));
+        assertFalse(CamelSendAction.isInsideAnIntegration("kafka:refunds"));
+        assertFalse(CamelSendAction.isInsideAnIntegration("jms:queue:inbox"));
+    }
+
     private static JsonObject sentResponse() {
         JsonObject response = new JsonObject();
         response.put("timestamp", FIXED_TIMESTAMP);
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt 
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
index 72caf84e74e1..9b5fcecb2865 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
@@ -1710,8 +1710,10 @@ Options:
                             (Use commas to separate multiple repositories)
       --fresh               Make sure we use fresh (i.e. non-cached) resources
                             Default: false
-  <name>                    To use an existing running Camel integration for
-                            sending the message (name or pid)
+  <name>                    The running Camel integration to send to (name or
+                            pid). Without it, the integration that runs, unless
+                            --endpoint is a system outside the integrations,
+                            such as kafka:topic, which the CLI sends to itself
 
 With --repeat the message is sent that many times, with --interval between two
 (a range such as 100..500 draws each pause at random), and a summary is printed

Reply via email to