diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java index 087357f1cb9b6..a6cb3354b04a9 100644 --- a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java +++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteDevConsole.java @@ -317,7 +317,9 @@ private void includeProcessorsJson(ManagedRouteMBean mrb, JsonArray arr) { arr.add(jo); jo.put("id", mp.getProcessorId()); - jo.put("nodePrefixId", mp.getNodePrefixId()); + if (mp.getNodePrefixId() != null) { + jo.put("nodePrefixId", mp.getNodePrefixId()); + } if (mp.getSourceLocation() != null) { String loc = mp.getSourceLocation(); if (mp.getSourceLineNumber() != null) { diff --git a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java index 74b4ca1a78b39..645cd0d2638b0 100644 --- a/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java +++ b/core/camel-main/src/main/java/org/apache/camel/main/BaseMainSupport.java @@ -1691,7 +1691,7 @@ private void setTracerProperties( throws Exception { TracerConfigurationProperties config = mainConfigurationProperties.tracerConfig(); - setPropertiesOnTarget(camelContext, config, properties, "camel.tracer.", + setPropertiesOnTarget(camelContext, config, properties, "camel.trace.", failIfNotSet, true, autoConfiguredProperties); if (!config.isEnabled() && !config.isStandby()) { diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc index 1a36c8d9ae225..ff04e5be63a2a 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc @@ -2399,6 +2399,54 @@ which is due to invalid configuration: `Invalid url in bootstrap.servers: value` TIP: Use `camel get health --help` to see all the various options. + +== Running with Spring Boot or Quarkus + +Camel JBang is __primary__ intended to be Camel standalone only. In *Camel 4.6* onwards we added limited +support for running with Spring Boot or Quarkus, but there are some limitations. + +You use the `--runtime` option to specify which platform to use, as shown below: + +[source,bash] +---- +camel run foo.camel.yaml --runtime=spring-boot +---- + +And for Quarkus: + +[source,bash] +---- +camel run foo.camel.yaml --runtime=quarkus +---- + +When running this way, then Camel JBang is _essentially_ doing an _export_ to a temporary folder, +and then running Spring Boot or Quarkus using Maven. + +You can do changes to the source file and have Quarkus and Spring Boot reload the routes, just as `camel run --dev` can do, +but uses the natural Spring Boot _dev-tools_ and Quarkus _dev mode_ functionality. + +There are several limitations, one would be that Spring Boot and Quarkus cannot automatically detect new components and download JARs. +(you can stop and run again to update dependencies). + +When using Quarkus then you can only select the Quarkus version to run, that are locked to a specific Camel version. +You can see the versions by `camel version list --runtime=quarkus`. On the other hand Spring Boot is more flexible +where you can choose different Spring Boot and Camel versions (within reasonable range). + +For example: + +[source,bash] +---- +camel run foo.camel.yaml --runtime=spring-boot --spring-boot-version=3.2.3 --camel-version=4.4.1 +---- + +And for Quarkus: + +[source,bash] +---- +camel run foo.camel.yaml --runtime=quarkus --quarkus-version=3.9.4 +---- + + == Transforming message (data mapping) When integrating system you often need to transform messages from one system to another. Camel has rich set diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java index edb9da8c35488..24f0803d95cfb 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java @@ -126,10 +126,10 @@ abstract class ExportBaseCommand extends CamelCommand { @CommandLine.Option(names = { "--main-classname" }, description = "The class name of the Camel Main application class", defaultValue = "CamelApplication") - protected String mainClassname; + protected String mainClassname = "CamelApplication"; @CommandLine.Option(names = { "--java-version" }, description = "Java version", defaultValue = "17") - protected String javaVersion; + protected String javaVersion = "17"; @CommandLine.Option(names = { "--camel-version" }, description = "To export using a different Camel version than the default version.") @@ -149,34 +149,34 @@ abstract class ExportBaseCommand extends CamelCommand { @CommandLine.Option(names = { "--spring-boot-version" }, description = "Spring Boot version", defaultValue = "3.2.5") - protected String springBootVersion; + protected String springBootVersion = "3.2.5"; @CommandLine.Option(names = { "--camel-spring-boot-version" }, description = "Camel version to use with Spring Boot") protected String camelSpringBootVersion; @CommandLine.Option(names = { "--quarkus-group-id" }, description = "Quarkus Platform Maven groupId", defaultValue = "io.quarkus.platform") - protected String quarkusGroupId; + protected String quarkusGroupId = "io.quarkus.platform"; @CommandLine.Option(names = { "--quarkus-artifact-id" }, description = "Quarkus Platform Maven artifactId", defaultValue = "quarkus-bom") - protected String quarkusArtifactId; + protected String quarkusArtifactId = "quarkus-bom"; @CommandLine.Option(names = { "--quarkus-version" }, description = "Quarkus Platform version", defaultValue = "3.9.4") - protected String quarkusVersion; + protected String quarkusVersion = "3.9.4"; @CommandLine.Option(names = { "--maven-wrapper" }, defaultValue = "true", description = "Include Maven Wrapper files in exported project") - protected boolean mavenWrapper; + protected boolean mavenWrapper = true; @CommandLine.Option(names = { "--gradle-wrapper" }, defaultValue = "true", description = "Include Gradle Wrapper files in exported project") - protected boolean gradleWrapper; + protected boolean gradleWrapper = true; @CommandLine.Option(names = { "--build-tool" }, defaultValue = "maven", description = "Build tool to use (maven or gradle)") - protected String buildTool; + protected String buildTool = "maven"; @CommandLine.Option(names = { "--open-api" }, description = "Adds an OpenAPI spec from the given file (json or yaml file)") protected String openapi; @@ -187,12 +187,11 @@ abstract class ExportBaseCommand extends CamelCommand { protected String exportDir; @CommandLine.Option(names = { "--logging-level" }, defaultValue = "info", description = "Logging level") - protected String loggingLevel; + protected String loggingLevel = "info"; @CommandLine.Option(names = { "--package-name" }, description = "For Java source files should they have the given package name. By default the package name is computed from the Maven GAV. " - + - "Use false to turn off and not include package name in the Java source files.") + + "Use false to turn off and not include package name in the Java source files.") protected String packageName; @CommandLine.Option(names = { "--fresh" }, description = "Make sure we use fresh (i.e. non-cached) resources") @@ -218,6 +217,8 @@ abstract class ExportBaseCommand extends CamelCommand { description = "Whether to ignore route loading and compilation errors (use this with care!)") protected boolean ignoreLoadingError; + protected boolean symbolicLink; // copy source files using symbolic link + public ExportBaseCommand(CamelJBangMain main) { super(main); } @@ -710,7 +711,7 @@ protected static String jibMavenPluginVersion(File settings) { return "3.4.0"; } - protected static void safeCopy(File source, File target, boolean override) throws Exception { + protected void safeCopy(File source, File target, boolean override) throws Exception { if (!source.exists()) { return; } @@ -728,6 +729,21 @@ protected static void safeCopy(File source, File target, boolean override) throw return; } + if (symbolicLink) { + try { + // must use absolute paths + Path link = target.toPath().toAbsolutePath(); + Path src = source.toPath().toAbsolutePath(); + if (Files.exists(link)) { + Files.delete(link); + } + Files.createSymbolicLink(link, src); + return; // success + } catch (IOException e) { + // ignore + } + } + if (!target.exists()) { Files.copy(source.toPath(), target.toPath()); } else if (override) { diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java index c9d8b015b7180..e713975b98dc5 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java @@ -56,6 +56,7 @@ import org.apache.camel.catalog.DefaultCamelCatalog; import org.apache.camel.dsl.jbang.core.common.CommandLineHelper; import org.apache.camel.dsl.jbang.core.common.LoggingLevelCompletionCandidates; +import org.apache.camel.dsl.jbang.core.common.RuntimeCompletionCandidates; import org.apache.camel.dsl.jbang.core.common.RuntimeUtil; import org.apache.camel.dsl.jbang.core.common.VersionHelper; import org.apache.camel.generator.openapi.RestDslGenerator; @@ -86,6 +87,7 @@ public class Run extends CamelCommand { public static final String RUN_SETTINGS_FILE = "camel-jbang-run.properties"; + private static final String RUN_PLATFORM_DIR = ".camel-jbang-run"; private static final String[] ACCEPTED_FILE_EXT = new String[] { "java", "groovy", "js", "jsh", "kts", "xml", "yaml" }; @@ -126,6 +128,10 @@ public class Run extends CamelCommand { public List files = new ArrayList<>(); + @Option(names = { "--runtime" }, completionCandidates = RuntimeCompletionCandidates.class, + defaultValue = "camel-main", description = "Runtime (spring-boot, quarkus, or camel-main)") + String runtime = "camel-main"; + @Option(names = { "--source-dir" }, description = "Source directory for dynamically loading Camel file(s) to run. When using this, then files cannot be specified at the same time.") String sourceDir; @@ -142,6 +148,14 @@ public class Run extends CamelCommand { @Option(names = { "--kamelets-version" }, description = "Apache Camel Kamelets version") String kameletsVersion; + @Option(names = { "--quarkus-version" }, description = "Quarkus Platform version", + defaultValue = "3.9.4") + String quarkusVersion = "3.9.4"; + + @Option(names = { "--spring-boot-version" }, description = "Spring Boot version", + defaultValue = "3.2.5") + String springBootVersion = "3.2.5"; + @Option(names = { "--profile" }, scope = CommandLine.ScopeType.INHERIT, defaultValue = "dev", description = "Profile to run (dev, test, or prod).") String profile = "dev"; @@ -289,6 +303,11 @@ public Run(CamelJBangMain main) { @Override public boolean disarrangeLogging() { + if (runtime.equals("quarkus")) { + return true; + } else if (runtime.equals("spring-boot")) { + return true; + } return false; } @@ -391,6 +410,12 @@ private int run() throws Exception { return 1; } + if (runtime.equals("quarkus")) { + return runQuarkus(); + } else if (runtime.equals("spring-boot")) { + return runSpringBoot(); + } + File work = CommandLineHelper.getWorkDir(); removeDir(work); work.mkdirs(); @@ -823,6 +848,145 @@ private int run() throws Exception { } } + protected int runQuarkus() throws Exception { + // create temp run dir + File runDir = new File(RUN_PLATFORM_DIR, "" + System.currentTimeMillis()); + if (!this.background) { + runDir.deleteOnExit(); + } + + // export to hidden folder + ExportQuarkus eq = new ExportQuarkus(getMain()); + eq.symbolicLink = true; + eq.quarkusVersion = this.quarkusVersion; + eq.camelVersion = this.camelVersion; + eq.kameletsVersion = this.kameletsVersion; + eq.exportDir = runDir.toString(); + eq.exclude = this.exclude; + eq.filePaths = this.filePaths; + eq.files = this.files; + eq.gav = this.gav; + if (eq.gav == null) { + eq.gav = "org.apache.camel:jbang-run-dummy:1.0-SNAPSHOT"; + } + eq.dependencies = this.dependencies; + if (eq.dependencies == null) { + eq.dependencies = "camel:cli-connector"; + } else { + eq.dependencies += ",camel:cli-connector"; + } + eq.fresh = this.fresh; + eq.download = this.download; + eq.quiet = true; + eq.logging = false; + eq.loggingLevel = "off"; + + System.out.println("Running using Quarkus v" + eq.quarkusVersion + " (preparing and downloading files)"); + + // run export + int exit = eq.export(); + if (exit != 0) { + return exit; + } + // run quarkus via maven + String mvnw = "/mvnw"; + if (FileUtil.isWindows()) { + mvnw = "/mvnw.cmd"; + } + ProcessBuilder pb = new ProcessBuilder(); + pb.command(runDir + mvnw, "--quiet", "--file", runDir.toString(), "quarkus:dev"); + + if (background) { + Process p = pb.start(); + this.spawnPid = p.pid(); + if (!silentRun && !transformRun && !transformMessageRun) { + printer().println("Running Camel Quarkus integration: " + name + " (version: " + eq.quarkusVersion + + ") in background"); + } + return 0; + } else { + pb.inheritIO(); // run in foreground (with IO so logs are visible) + Process p = pb.start(); + this.spawnPid = p.pid(); + // wait for that process to exit as we run in foreground + return p.waitFor(); + } + } + + protected int runSpringBoot() throws Exception { + // create temp run dir + File runDir = new File(RUN_PLATFORM_DIR, "" + System.currentTimeMillis()); + if (!this.background) { + runDir.deleteOnExit(); + } + + // export to hidden folder + ExportSpringBoot eq = new ExportSpringBoot(getMain()); + eq.symbolicLink = true; + eq.springBootVersion = this.springBootVersion; + eq.camelVersion = this.camelVersion; + eq.camelSpringBootVersion = this.camelVersion; + eq.kameletsVersion = this.kameletsVersion; + eq.exportDir = runDir.toString(); + eq.exclude = this.exclude; + eq.filePaths = this.filePaths; + eq.files = this.files; + eq.gav = this.gav; + if (eq.gav == null) { + eq.gav = "org.apache.camel:jbang-run-dummy:1.0-SNAPSHOT"; + } + eq.dependencies = this.dependencies; + if (eq.dependencies == null) { + eq.dependencies = "camel:cli-connector"; + } else { + eq.dependencies += ",camel:cli-connector"; + } + if (this.dev) { + // hot-reload of spring-boot + eq.dependencies += ",mvn:org.springframework.boot:spring-boot-devtools"; + } + eq.fresh = this.fresh; + eq.download = this.download; + eq.quiet = true; + eq.logging = false; + eq.loggingLevel = "off"; + + System.out.println("Running using Spring Boot v" + eq.springBootVersion + " (preparing and downloading files)"); + + // run export + int exit = eq.export(); + if (exit != 0) { + return exit; + } + // prepare spring-boot for logging to file + InputStream is = Run.class.getClassLoader().getResourceAsStream("spring-boot-logback.xml"); + eq.safeCopy(is, new File(eq.exportDir + "/src/main/resources/logback.xml")); + + // run spring-boot via maven + ProcessBuilder pb = new ProcessBuilder(); + String mvnw = "/mvnw"; + if (FileUtil.isWindows()) { + mvnw = "/mvnw.cmd"; + } + pb.command(runDir + mvnw, "--quiet", "--file", runDir.toString(), "spring-boot:run"); + + if (background) { + Process p = pb.start(); + this.spawnPid = p.pid(); + if (!silentRun && !transformRun && !transformMessageRun) { + printer().println("Running Camel Spring Boot integration: " + name + " (version: " + camelVersion + + ") in background"); + } + return 0; + } else { + pb.inheritIO(); // run in foreground (with IO so logs are visible) + Process p = pb.start(); + this.spawnPid = p.pid(); + // wait for that process to exit as we run in foreground + return p.waitFor(); + } + } + private boolean acceptPropertiesFile(String file) { String name = FileUtil.onlyName(file); if (profile != null && name.startsWith("application-")) { diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/spring-boot-logback.xml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/spring-boot-logback.xml new file mode 100644 index 0000000000000..07ec3874841cf --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/spring-boot-logback.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + \ No newline at end of file