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 19334bdb35c8 CAMEL-23615: camel-jbang - Add CLI restart command
19334bdb35c8 is described below

commit 19334bdb35c8d5c670a49bbd768a50559b34032a
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu May 28 13:21:30 2026 +0200

    CAMEL-23615: camel-jbang - Add CLI restart command
    
    Add `camel restart <name|pid>` command that stops a running Camel
    integration and re-launches it with the same command line and working
    directory. Uses ProcessHandle.info() to capture the original Java command
    line before stopping, performs graceful shutdown with a 10s timeout, then
    re-launches via ProcessBuilder.
    
    Closes #23596
---
 .../pages/jbang-commands/camel-jbang-commands.adoc |   1 +
 .../pages/jbang-commands/camel-jbang-restart.adoc  |  25 ++++
 .../META-INF/camel-jbang-commands-metadata.json    |   1 +
 .../dsl/jbang/core/commands/CamelJBangMain.java    |   1 +
 .../core/commands/process/RestartProcess.java      | 164 +++++++++++++++++++++
 5 files changed, 192 insertions(+)

diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc
index 48a42450ea20..007bc78d9982 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-commands.adoc
@@ -35,6 +35,7 @@ TIP: You can also use `camel --help` or `camel <command> 
--help` to see availabl
 | xref:jbang-commands/camel-jbang-nano.adoc[camel nano] | Nano editor to edit 
file
 | xref:jbang-commands/camel-jbang-plugin.adoc[camel plugin] | Manage plugins 
that add sub-commands to this CLI
 | xref:jbang-commands/camel-jbang-ps.adoc[camel ps] | List running Camel 
integrations
+| xref:jbang-commands/camel-jbang-restart.adoc[camel restart] | Restarts 
running Camel integrations (stop + re-launch)
 | xref:jbang-commands/camel-jbang-run.adoc[camel run] | Run as local Camel 
integration
 | xref:jbang-commands/camel-jbang-sbom.adoc[camel sbom] | Generate a CycloneDX 
or SPDX SBOM for a specific project
 | xref:jbang-commands/camel-jbang-script.adoc[camel script] | Run Camel 
integration as shell script for terminal scripting
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-restart.adoc 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-restart.adoc
new file mode 100644
index 000000000000..3e9f6ff54b43
--- /dev/null
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-restart.adoc
@@ -0,0 +1,25 @@
+
+// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE
+= camel restart
+
+Restarts running Camel integrations (stop + re-launch)
+
+
+== Usage
+
+[source,bash]
+----
+camel restart [options]
+----
+
+
+
+== Options
+
+[cols="2,5,1,2",options="header"]
+|===
+| Option | Description | Default | Type
+| `-h,--help` | Display the help and sub-commands |  | boolean
+|===
+
+
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index 3333298402bb..d8fc3f98ab8f 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -24,6 +24,7 @@
     { "name": "nano", "fullName": "nano", "description": "Nano editor to edit 
file", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Nano", 
"options": [ { "names": "-h,--help", "description": "Display the help and 
sub-commands", "javaType": "boolean", "type": "boolean" } ] },
     { "name": "plugin", "fullName": "plugin", "description": "Manage plugins 
that add sub-commands to this CLI", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.plugin.PluginCommand", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "add", 
"fullName": "plugin add", "description": "Add new plugin", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.plugin.PluginA [...]
     { "name": "ps", "fullName": "ps", "description": "List running Camel 
integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.ListProcess", "options": [ { 
"names": "--json", "description": "Output in JSON Format", "javaType": 
"boolean", "type": "boolean" }, { "names": "--pid", "description": "List only 
pid in the output", "javaType": "boolean", "type": "boolean" }, { "names": 
"--remote", "description": "Break down counters into remote\/total pairs", 
"javaType": [...]
+    { "name": "restart", "fullName": "restart", "description": "Restarts 
running Camel integrations (stop + re-launch)", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.RestartProcess", "options": [ 
{ "names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ] },
     { "name": "run", "fullName": "run", "description": "Run as local Camel 
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Run", 
"options": [ { "names": "--background", "description": "Run in the background", 
"defaultValue": "false", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background-wait", "description": "To wait for run in background to startup 
successfully, before returning", "defaultValue": "true", "javaType": "boolean", 
"type": "boolean" }, { [...]
     { "name": "sbom", "fullName": "sbom", "description": "Generate a CycloneDX 
or SPDX SBOM for a specific project", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.SBOMGenerator", "options": [ { 
"names": "--build-property", "description": "Maven build properties, ex. 
--build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, { 
"names": "--camel-spring-boot-version", "description": "Camel version to use 
with Spring Boot", "javaType": "java.lang.String", "type" [...]
     { "name": "script", "fullName": "script", "description": "Run Camel 
integration as shell script for terminal scripting", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Script", "options": [ { "names": 
"--logging", "description": "Can be used to turn on logging (logs to file in 
<user home>\/.camel directory)", "defaultValue": "false", "javaType": 
"boolean", "type": "boolean" }, { "names": "--logging-level", "description": 
"Logging level (ERROR, WARN, INFO, DEBUG, TRACE)", "d [...]
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 9d0996730950..5ffd1ecd473a 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
@@ -190,6 +190,7 @@ public class CamelJBangMain implements Callable<Integer> {
                         .addSubcommand("get", new CommandLine(new 
PluginGet(this)))
                         .addSubcommand("list", new CommandLine(new 
PluginList(this))))
                 .addSubcommand("ps", new CommandLine(new ListProcess(this)))
+                .addSubcommand("restart", new CommandLine(new 
RestartProcess(this)))
                 .addSubcommand("run", new CommandLine(new Run(this)))
                 .addSubcommand("sbom", new CommandLine(new 
SBOMGenerator(this)))
                 .addSubcommand("script", new CommandLine(new Script(this)))
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/RestartProcess.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/RestartProcess.java
new file mode 100644
index 000000000000..cd1f6dd0c067
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/RestartProcess.java
@@ -0,0 +1,164 @@
+/*
+ * 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.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.ProcessHelper;
+import org.apache.camel.util.json.JsonObject;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+@Command(name = "restart",
+         description = "Restarts running Camel integrations (stop + 
re-launch)", sortOptions = false,
+         showDefaultValues = true,
+         footer = {
+                 "%nExamples:",
+                 "  camel restart hello",
+                 "  camel restart 12345" })
+public class RestartProcess extends ProcessBaseCommand {
+
+    @CommandLine.Parameters(description = "Name or pid of running Camel 
integration", arity = "1")
+    String name;
+
+    public RestartProcess(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doCall() throws Exception {
+        List<Long> pids = findPids(name);
+        if (pids.isEmpty()) {
+            printer().println("No matching Camel integration found: " + name);
+            return 1;
+        }
+        if (pids.size() > 1) {
+            printer().println("Multiple integrations match '" + name + "'. 
Please be more specific.");
+            return 1;
+        }
+
+        long pid = pids.get(0);
+        ProcessHandle ph = ProcessHandle.of(pid).orElse(null);
+        if (ph == null) {
+            printer().println("Process not found: " + pid);
+            return 1;
+        }
+
+        // resolve name for display
+        String displayName = name;
+        JsonObject root = loadStatus(pid);
+        if (root != null) {
+            displayName = ProcessHelper.extractName(root, ph);
+        }
+
+        // capture command line before stopping
+        Optional<String> cmdOpt = ph.info().command();
+        Optional<String[]> argsOpt = ph.info().arguments();
+        Optional<String> cmdLineOpt = ph.info().commandLine();
+
+        // get working directory from status file
+        String directory = null;
+        if (root != null) {
+            JsonObject runtime = (JsonObject) root.get("runtime");
+            if (runtime != null) {
+                directory = runtime.getString("directory");
+            }
+        }
+
+        // build the command to re-launch
+        List<String> cmd = new ArrayList<>();
+        if (cmdOpt.isPresent() && argsOpt.isPresent() && argsOpt.get().length 
> 0) {
+            cmd.add(cmdOpt.get());
+            Collections.addAll(cmd, argsOpt.get());
+        } else if (cmdLineOpt.isPresent()) {
+            cmd.addAll(parseCommandLine(cmdLineOpt.get()));
+        }
+
+        if (cmd.isEmpty()) {
+            printer().println("Cannot restart: command line not available for 
PID " + pid);
+            return 1;
+        }
+
+        // stop the process gracefully
+        printer().println("Stopping Camel integration: " + displayName + " 
(PID: " + pid + ")");
+        ph.destroy();
+
+        // wait for termination
+        CompletableFuture<ProcessHandle> exitFuture = 
ph.onExit().toCompletableFuture();
+        try {
+            exitFuture.get(10, TimeUnit.SECONDS);
+        } catch (Exception e) {
+            printer().println("Graceful shutdown timed out, force killing...");
+            ph.destroyForcibly();
+            Thread.sleep(500);
+        }
+
+        // re-launch
+        ProcessBuilder pb = new ProcessBuilder(cmd);
+        if (directory != null) {
+            pb.directory(new File(directory));
+        }
+        pb.redirectErrorStream(true);
+        Path outputFile = Files.createTempFile("camel-restart-", ".log");
+        outputFile.toFile().deleteOnExit();
+        pb.redirectOutput(outputFile.toFile());
+        Process process = pb.start();
+
+        printer().println("Restarted Camel integration: " + displayName + " 
(PID: " + process.pid() + ")");
+
+        return 0;
+    }
+
+    static List<String> parseCommandLine(String commandLine) {
+        List<String> tokens = new ArrayList<>();
+        StringBuilder current = new StringBuilder();
+        boolean inQuotes = false;
+        boolean escaped = false;
+
+        for (int i = 0; i < commandLine.length(); i++) {
+            char c = commandLine.charAt(i);
+            if (escaped) {
+                current.append(c);
+                escaped = false;
+            } else if (c == '\\') {
+                escaped = true;
+            } else if (c == '"') {
+                inQuotes = !inQuotes;
+            } else if (c == ' ' && !inQuotes) {
+                if (!current.isEmpty()) {
+                    tokens.add(current.toString());
+                    current.setLength(0);
+                }
+            } else {
+                current.append(c);
+            }
+        }
+        if (!current.isEmpty()) {
+            tokens.add(current.toString());
+        }
+        return tokens;
+    }
+}

Reply via email to