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 dcc56d35d427 CAMEL-26929: camel-jbang - a name of a running 
integration matches only that integration (#29909)
dcc56d35d427 is described below

commit dcc56d35d427023a37a27994d50be572045067be
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Nov 14 19:22:31 2026 +0100

    CAMEL-26929: camel-jbang - a name of a running integration matches only 
that integration (#29909)
    
    A name is a prefix unless it ends with !, so with orders and orders-v2 
running,
    camel stop orders stopped both, and every command that needs one integration
    failed for orders because the name matched two.
    
    When an integration has exactly the given name (its name, file name or
    CamelContext name), the name now matches only the integrations of that 
name. A
    name that no integration has stays a prefix; orders*, orders!, patterns and 
pids
    are unchanged. Every command and the runtime tools of the MCP server find
    integrations through ProcessFinder, so this covers them all.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  9 +++++
 .../modules/ROOT/pages/camel-jbang-managing.adoc   |  5 +--
 .../camel/dsl/jbang/core/common/ProcessFinder.java | 26 ++++++++++++--
 .../dsl/jbang/core/common/ProcessFinderTest.java   | 41 ++++++++++++++++++++--
 4 files changed, 74 insertions(+), 7 deletions(-)

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 028422a474ff..3e3659e01173 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
@@ -1459,6 +1459,15 @@ with code `1` (fix the route) instead of `70` (internal 
error), and prints the e
 The stack trace is in the log of the integration, and is no longer printed a 
second time on stderr (add `--verbose`
 for it). Scripts that checked for `70` after a failed start should check for 
`1`.
 
+=== camel-jbang - a name of a running integration matches only that integration
+
+A name given to `camel stop`, `camel get`, `camel cmd`, `camel trace` and the 
other commands for running integrations
+is still a prefix, but when an integration has exactly that name, the name now 
matches only that integration. With
+`orders` and `orders-v2` running, `camel stop orders` stops only `orders` 
(before, it stopped both), and
+`camel cmd send orders` sends to `orders` (before, it failed because the name 
matched two integrations). The same
+applies to the CamelContext name, and to the `nameOrPid` argument of the 
runtime tools of the MCP server. To act on
+all integrations whose names start with `orders`, use `orders*`.
+
 === camel-resilience4j, camel-microprofile-fault-tolerance - a circuit breaker 
in a doTry
 
 A circuit breaker in the steps of a `doTry` no longer ends that `doTry` for 
the steps after it. Before, when the
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
index 83a171338af3..857c05a20a6e 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
@@ -33,8 +33,9 @@ Shutting down Camel integration (PID: 62506)
 `camel stop` waits until the integration has stopped, up to `--timeout` (60 
seconds by default). When it has not
 stopped in time, it exits with code `124`, and `--kill` stops it at once. Use 
`--no-wait` to return without waiting.
 
-NOTE: The name is matched as a prefix — `camel stop d` stops all integrations 
starting with _d_.
-To stop exactly one when names overlap (e.g., `dude` and `dude2`), use the PID 
or append `!`: `camel stop dude!`.
+NOTE: The name is matched as a prefix — `camel stop d` stops all integrations 
starting with _d_ — unless an
+integration has exactly that name: with `dude` and `dude2` running, `camel 
stop dude` stops only `dude`, and
+`camel stop dude*` stops both. Append `!` to match only that name (`camel stop 
dude!`), or give the PID.
 A PID must be a running Camel integration, and a name or PID that matches 
nothing exits with code `3`
 (see xref:camel-jbang-exit-codes.adoc[Exit Codes and Output]).
 
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ProcessFinder.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ProcessFinder.java
index 650b4efd9391..59b3f7fdd4af 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ProcessFinder.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ProcessFinder.java
@@ -40,7 +40,9 @@ import org.apache.camel.util.json.Jsoner;
  * <p/>
  * A number is a pid. A name matches the name of the integration (as <tt>camel 
ps</tt> shows it, without file extension)
  * or its CamelContext name: it is a prefix (<tt>foo</tt> matches 
<tt>foo-bar</tt>) unless it ends with <tt>!</tt>
- * (<tt>foo!</tt> matches only <tt>foo</tt>), and it may use wildcards and 
regular expressions.
+ * (<tt>foo!</tt> matches only <tt>foo</tt>), and it may use wildcards and 
regular expressions. When an integration has
+ * exactly the given name, the name matches only the integrations of that 
name: with <tt>foo</tt> and <tt>foo-bar</tt>
+ * running, <tt>foo</tt> matches only <tt>foo</tt>, and <tt>fo</tt> both.
  */
 public final class ProcessFinder {
 
@@ -119,7 +121,7 @@ public final class ProcessFinder {
         if (found.size() > 1) {
             throw CliException.notFound("Name or pid " + nameOrPid + " matches 
" + found.size()
                                         + " running Camel integrations: " + 
describe(found)
-                                        + ". Specify a name or pid that 
matches exactly one.");
+                                        + ". Specify the pid, or a name that 
matches exactly one.");
         }
         return found.get(0);
     }
@@ -228,9 +230,29 @@ public final class ProcessFinder {
                     }
                 });
         answer.sort(Comparator.comparingLong(CamelProcess::pid));
+        if (answer.size() > 1 && isPlainName(nameOrPid, byPid)) {
+            // orders matches orders-v2 too, unless an integration is named 
orders
+            List<CamelProcess> same = answer.stream().filter(p -> hasName(p, 
nameOrPid)).toList();
+            if (!same.isEmpty()) {
+                return new ArrayList<>(same);
+            }
+        }
         return answer;
     }
 
+    // a name as it is given, not a pid, an exact name (foo!) or a pattern 
(foo*)
+    private static boolean isPlainName(String nameOrPid, boolean byPid) {
+        return nameOrPid != null && !byPid && !nameOrPid.endsWith("!") && 
!nameOrPid.endsWith("*");
+    }
+
+    private static boolean hasName(CamelProcess p, String name) {
+        if (p.name() != null && (p.name().equalsIgnoreCase(name) || 
name.equalsIgnoreCase(FileUtil.onlyName(p.name())))) {
+            return true;
+        }
+        String contextName = p.contextName();
+        return contextName != null && !"CamelJBang".equals(contextName) && 
contextName.equalsIgnoreCase(name);
+    }
+
     private static String pattern(String name) {
         if (name.endsWith("!")) {
             // exactly this name
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ProcessFinderTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ProcessFinderTest.java
index 62372909aad1..e333e3aac164 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ProcessFinderTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ProcessFinderTest.java
@@ -118,6 +118,39 @@ class ProcessFinderTest {
         
assertThat(ProcessFinder.find("orders-(eu|us)")).extracting(CamelProcess::pid).containsExactly(200L);
     }
 
+    @Test
+    void aNameOfARunningIntegrationMatchesOnlyThatIntegration() throws 
Exception {
+        running(100, "orders", 5);
+        running(200, "orders-v2", 5);
+        running(300, "orders-eu", 5);
+
+        // camel stop orders must not stop orders-v2 and orders-eu
+        
assertThat(ProcessFinder.find("orders")).extracting(CamelProcess::pid).containsExactly(100L);
+        
assertThat(ProcessFinder.find("ORDERS")).extracting(CamelProcess::pid).containsExactly(100L);
+        
assertThat(ProcessFinder.requireAny("orders")).extracting(CamelProcess::pid).containsExactly(100L);
+        // no integration has these names, so they stay prefixes and patterns
+        
assertThat(ProcessFinder.find("order")).extracting(CamelProcess::pid).containsExactly(100L,
 200L, 300L);
+        
assertThat(ProcessFinder.find("orders*")).extracting(CamelProcess::pid).containsExactly(100L,
 200L, 300L);
+        
assertThat(ProcessFinder.find("orders-e")).extracting(CamelProcess::pid).containsExactly(300L);
+        assertThat(ProcessFinder.find("*")).hasSize(3);
+    }
+
+    @Test
+    void 
aFileNameOrContextNameOfARunningIntegrationMatchesOnlyThatIntegration() throws 
Exception {
+        // the name comes from the main class, and the context has a name of 
its own
+        JsonObject root = statusOf("billing", 5);
+        JsonObject runtime = new JsonObject();
+        runtime.put("mainClass", "Orders");
+        root.put("runtime", runtime);
+        
Files.writeString(CommandLineHelper.getCamelDir().resolve("100-status.json"), 
root.toJson());
+        processes.add(handle(100));
+        running(200, "billing-eu", 5);
+        running(300, "orders-v2", 5);
+
+        
assertThat(ProcessFinder.find("billing")).extracting(CamelProcess::pid).containsExactly(100L);
+        
assertThat(ProcessFinder.find("orders")).extracting(CamelProcess::pid).containsExactly(100L);
+    }
+
     @Test
     void theDefaultContextNameOfTheCliMatchesNothing() throws Exception {
         // the name comes from the main class, and the context has the default 
name of the Camel CLI
@@ -174,10 +207,12 @@ class ProcessFinderTest {
                 .isInstanceOf(CliException.class)
                 .hasMessage("No running Camel integration matches: billing")
                 .extracting(e -> ((CliException) 
e).getExitCode()).isEqualTo(CliExitCode.NOT_FOUND);
-        assertThatThrownBy(() -> ProcessFinder.requireOne("orders"))
+        // an integration named orders runs, so orders is not ambiguous
+        assertThat(ProcessFinder.requireOne("orders").pid()).isEqualTo(100L);
+        assertThatThrownBy(() -> ProcessFinder.requireOne("ord"))
                 .isInstanceOf(CliException.class)
-                .hasMessage("Name or pid orders matches 2 running Camel 
integrations: 100 (orders), 200 (orders-eu)."
-                            + " Specify a name or pid that matches exactly 
one.")
+                .hasMessage("Name or pid ord matches 2 running Camel 
integrations: 100 (orders), 200 (orders-eu)."
+                            + " Specify the pid, or a name that matches 
exactly one.")
                 .extracting(e -> ((CliException) 
e).getExitCode()).isEqualTo(CliExitCode.NOT_FOUND);
     }
 

Reply via email to