Skip to content

Commit

Permalink
camel-jbang - run with spring-boot or quarkus (#13919)
Browse files Browse the repository at this point in the history
CAMEL-19041: camel-jbang - Run with runtime for spring-boot and quarkus
  • Loading branch information
davsclaus authored Apr 24, 2024
1 parent 79e147d commit 25bedbb
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
48 changes: 48 additions & 0 deletions docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand All @@ -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;
Expand All @@ -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")
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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" };
Expand Down Expand Up @@ -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;
Expand All @@ -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";
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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-")) {
Expand Down
Loading

0 comments on commit 25bedbb

Please sign in to comment.