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;
+ }
+}