This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-23615-cli-restart in repository https://gitbox.apache.org/repos/asf/camel.git
commit 8f56fc37f2ef3f85d1a82a79edc46bd21706e70f Author: Claus Ibsen <[email protected]> AuthorDate: Thu May 28 11:33:47 2026 +0200 CAMEL-23615: camel-jbang - Add CLI restart command Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../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; + } +}
