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