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 25bedbb34f2 camel-jbang - run with spring-boot or quarkus (#13919)
25bedbb34f2 is described below

commit 25bedbb34f2adbfed0a215d954e3caec10207098
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Apr 24 21:12:49 2024 +0200

    camel-jbang - run with spring-boot or quarkus (#13919)
    
    CAMEL-19041: camel-jbang - Run with runtime for spring-boot and quarkus
---
 .../apache/camel/impl/console/RouteDevConsole.java |   4 +-
 .../org/apache/camel/main/BaseMainSupport.java     |   2 +-
 .../modules/ROOT/pages/camel-jbang.adoc            |  48 ++++++
 .../dsl/jbang/core/commands/ExportBaseCommand.java |  42 ++++--
 .../apache/camel/dsl/jbang/core/commands/Run.java  | 164 +++++++++++++++++++++
 .../src/main/resources/spring-boot-logback.xml     |  29 ++++
 6 files changed, 274 insertions(+), 15 deletions(-)

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 087357f1cb9..a6cb3354b04 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 @@ public class RouteDevConsole extends AbstractDevConsole {
             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 74b4ca1a78b..645cd0d2638 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 @@ public abstract class BaseMainSupport extends BaseService 
{
             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 1a36c8d9ae2..ff04e5be63a 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 edb9da8c354..24f0803d95c 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 @@ abstract class ExportBaseCommand extends CamelCommand {
         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 @@ abstract class ExportBaseCommand extends CamelCommand {
             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 c9d8b015b71..e713975b98d 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.CamelCatalog;
 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 @@ import static 
org.apache.camel.dsl.jbang.core.common.GitHubHelper.fetchGithubUrl
 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<String> 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 class Run extends CamelCommand {
 
     @Override
     public boolean disarrangeLogging() {
+        if (runtime.equals("quarkus")) {
+            return true;
+        } else if (runtime.equals("spring-boot")) {
+            return true;
+        }
         return false;
     }
 
@@ -391,6 +410,12 @@ public class Run extends CamelCommand {
             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 @@ public class Run extends CamelCommand {
         }
     }
 
+    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 00000000000..07ec3874841
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/spring-boot-logback.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<configuration>
+    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
+    <include 
resource="org/springframework/boot/logging/logback/console-appender.xml" />
+    <include 
resource="org/springframework/boot/logging/logback/file-appender.xml" />
+    <property name="LOG_FILE" 
value="${user.home}${file.separator}.camel${file.separator}${PID}.log"/>
+    <root level="INFO">
+        <appender-ref ref="CONSOLE" />
+        <appender-ref ref="FILE" />
+    </root>
+</configuration>
\ No newline at end of file

Reply via email to