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 4837e7ba6cae CAMEL-25422, CAMEL-25423: camel-jbang - run existing
Quarkus projects and examples on all runtimes (#27514)
4837e7ba6cae is described below
commit 4837e7ba6cae16926c7e4c50458d315712965551
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Oct 7 23:23:30 2026 +0200
CAMEL-25422, CAMEL-25423: camel-jbang - run existing Quarkus projects and
examples on all runtimes (#27514)
* CAMEL-25422: camel-jbang - detect Quarkus projects that name the BOM with
properties
camel run pom.xml ran a project generated by code.quarkus.io (and the
camel-quarkus-examples) as a plain Camel Main project, because the BOM is
declared as ${quarkus.platform.artifact-id}. Resolve the pom properties
before matching, and also detect the runtime from the quarkus-maven-plugin
or spring-boot-maven-plugin build plugin.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
* CAMEL-25423: camel-jbang - run Camel Main and Spring Boot apps in the
current directory
camel run --runtime=camel-main and --runtime=spring-boot started the
exported
app in the export folder (.camel-jbang-run/<time>), so routes that read or
write files relative to the working directory used that folder instead. An
example run with these runtimes processed nothing, as file:orders did not
see
the example's orders folder. Run them in the current directory, as JBang and
Quarkus do.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
* CAMEL-25426: camel-jbang - start the infra service of the aws-sqs example
with its implementation
The aws-sqs example needs camel infra run aws sqs, but its catalog entry
said
aws, which fails with "service aws not found", so the example could not
start
from the example browser. The catalog entry now says aws sqs (also fixed in
camel-jbang-examples), and the launcher passes the implementation on to
camel infra run, while it knows the running service by its name, aws.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
* CAMEL-25422: camel-jbang - a pom property that refers to itself does not
loop
Resolving ${a} -> ${b} -> ${a} recursed until StackOverflowError, which the
catch of Exception did not stop. It now follows at most 10 references.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
---------
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
.../apache/camel/dsl/jbang/core/commands/Run.java | 6 +-
.../camel/dsl/jbang/core/commands/RunHelper.java | 38 +++++-
.../examples/camel-jbang-example-catalog.json | 2 +-
.../core/commands/RunHelperDetectRuntimeTest.java | 152 +++++++++++++++++++++
.../dsl/jbang/core/commands/tui/LaunchManager.java | 22 ++-
.../core/commands/tui/LaunchManagerInfraTest.java | 59 ++++++++
6 files changed, 270 insertions(+), 9 deletions(-)
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 60b810c670a5..69dbb782ac58 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
@@ -1930,7 +1930,8 @@ public class Run extends CamelCommand {
pb = new ProcessBuilder();
pb.command(javaCmd);
- pb.directory(runDirPath.toFile());
+ // run in the current directory (not the export folder), so the routes
read and write files relative to
+ // where camel run is started, as with JBang: file:orders in an
example reads its orders folder (CAMEL-25423)
pb.inheritIO(); // run in foreground (with IO so logs are visible)
p = pb.start();
processRef.set(p);
@@ -2450,6 +2451,9 @@ public class Run extends CamelCommand {
if (springBootRunJvmArgs != null) {
mvnCmd.add("-Dspring-boot.run.jvmArguments=" +
springBootRunJvmArgs);
}
+ // run in the current directory, not the export folder, so the routes
read and write files relative to where
+ // camel run is started (CAMEL-25423)
+ mvnCmd.add("-Dspring-boot.run.workingDirectory=" +
Paths.get(".").toAbsolutePath().normalize());
mvnCmd.add("spring-boot:run");
pb.command(mvnCmd);
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/RunHelper.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/RunHelper.java
index dccf404e15c7..f9c0e2ef7b90 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/RunHelper.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/RunHelper.java
@@ -39,6 +39,7 @@ import org.apache.camel.util.FileUtil;
import org.apache.camel.util.ReflectionHelper;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
+import org.apache.maven.model.Plugin;
import org.apache.maven.model.Repository;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
@@ -79,9 +80,14 @@ public final class RunHelper {
public static RuntimeType detectRuntimeFromPom(Path pomPath) {
try {
Model model = loadMavenModel(pomPath);
- if (model != null && model.getDependencyManagement() != null) {
+ if (model == null) {
+ return null;
+ }
+ if (model.getDependencyManagement() != null) {
for (Dependency d :
model.getDependencyManagement().getDependencies()) {
- String a = d.getArtifactId();
+ // a project generated by code.quarkus.io names its BOMs
with properties:
+ //
<artifactId>${quarkus.platform.artifact-id}</artifactId> (CAMEL-25422)
+ String a = resolveProperty(model, d.getArtifactId());
if ("camel-spring-boot-bom".equals(a) ||
"spring-boot-dependencies".equals(a)) {
return RuntimeType.springBoot;
}
@@ -90,12 +96,40 @@ public final class RunHelper {
}
}
}
+ // the Maven plugin of the runtime builds the application
+ if (model.getBuild() != null) {
+ for (Plugin plugin : model.getBuild().getPlugins()) {
+ String a = resolveProperty(model, plugin.getArtifactId());
+ if ("quarkus-maven-plugin".equals(a)) {
+ return RuntimeType.quarkus;
+ }
+ if ("spring-boot-maven-plugin".equals(a)) {
+ return RuntimeType.springBoot;
+ }
+ }
+ }
} catch (Exception e) {
// ignore
}
return null;
}
+ /** The value with a ${name} property of the pom resolved, as Maven would;
as written when the pom has none. */
+ static String resolveProperty(Model model, String value) {
+ // a few levels of ${a} -> ${b} -> value; a property that refers to
itself (or a cycle) stops there
+ for (int i = 0; i < 10; i++) {
+ if (value == null || !value.startsWith("${") ||
!value.endsWith("}") || model.getProperties() == null) {
+ return value;
+ }
+ String resolved =
model.getProperties().getProperty(value.substring(2, value.length() - 1));
+ if (resolved == null) {
+ return value;
+ }
+ value = resolved;
+ }
+ return value;
+ }
+
public static List<String> scanMavenDependenciesFromPom(Path pomPath)
throws Exception {
Model model = loadMavenModel(pomPath);
if (model != null) {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
index 3991cebcf06e..2175a3b5fdc8 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
@@ -213,7 +213,7 @@
"orders/order-1003.json"
],
"infraServices": [
- "aws"
+ "aws sqs"
],
"order": 1
},
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunHelperDetectRuntimeTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunHelperDetectRuntimeTest.java
new file mode 100644
index 000000000000..9f89cff73b0a
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/RunHelperDetectRuntimeTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.apache.camel.dsl.jbang.core.common.RuntimeType;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Verifies how {@code camel run pom.xml} tells the runtime of an existing
Maven project from its pom.
+ */
+class RunHelperDetectRuntimeTest {
+
+ @TempDir
+ Path dir;
+
+ @Test
+ void quarkusBomNamedWithProperties() throws Exception {
+ // as generated by code.quarkus.io and used by camel-quarkus-examples
+ Path pom = pom("""
+ <properties>
+
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
+
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
+ <quarkus.platform.version>3.30.0</quarkus.platform.version>
+ </properties>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>${quarkus.platform.group-id}</groupId>
+
<artifactId>${quarkus.platform.artifact-id}</artifactId>
+ <version>${quarkus.platform.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ """);
+
assertThat(RunHelper.detectRuntimeFromPom(pom)).isEqualTo(RuntimeType.quarkus);
+ }
+
+ @Test
+ void quarkusMavenPlugin() throws Exception {
+ Path pom = pom("""
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>io.quarkus.platform</groupId>
+ <artifactId>quarkus-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ """);
+
assertThat(RunHelper.detectRuntimeFromPom(pom)).isEqualTo(RuntimeType.quarkus);
+ }
+
+ @Test
+ void springBootBom() throws Exception {
+ Path pom = pom("""
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel.springboot</groupId>
+ <artifactId>camel-spring-boot-bom</artifactId>
+ <version>4.23.0</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ """);
+
assertThat(RunHelper.detectRuntimeFromPom(pom)).isEqualTo(RuntimeType.springBoot);
+ }
+
+ @Test
+ void springBootMavenPlugin() throws Exception {
+ Path pom = pom("""
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ """);
+
assertThat(RunHelper.detectRuntimeFromPom(pom)).isEqualTo(RuntimeType.springBoot);
+ }
+
+ @Test
+ void aPropertyThatRefersToItselfDoesNotLoop() throws Exception {
+ Path pom = pom("""
+ <properties>
+ <a>${b}</a>
+ <b>${a}</b>
+ </properties>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.example</groupId>
+ <artifactId>${a}</artifactId>
+ <version>1.0</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ """);
+ assertThat(RunHelper.detectRuntimeFromPom(pom)).isNull();
+ }
+
+ @Test
+ void plainProject() throws Exception {
+ Path pom = pom("""
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-main</artifactId>
+ </dependency>
+ </dependencies>
+ """);
+ assertThat(RunHelper.detectRuntimeFromPom(pom)).isNull();
+ }
+
+ private Path pom(String body) throws Exception {
+ Path pom = dir.resolve("pom.xml");
+ Files.writeString(pom, """
+ <project xmlns="http://maven.apache.org/POM/4.0.0">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.example</groupId>
+ <artifactId>demo</artifactId>
+ <version>1.0</version>
+ """ + body + "</project>\n");
+ return pom;
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LaunchManager.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LaunchManager.java
index 574c0c4c0e4f..7a13f17aba5f 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LaunchManager.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LaunchManager.java
@@ -24,6 +24,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
@@ -192,7 +193,7 @@ class LaunchManager {
.collect(Collectors.toSet());
List<String> missing = new ArrayList<>();
for (String alias : required) {
- if (!runningAliases.contains(alias)) {
+ if (!runningAliases.contains(serviceOf(alias))) {
missing.add(alias);
}
}
@@ -205,14 +206,25 @@ class LaunchManager {
}
/**
- * Starts an infra service in the background via {@code camel infra run
<alias> --background}. The launch is
- * monitored like any other, so a failure surfaces through the failure log
callback.
+ * The service of an infra entry, which may name its implementation too:
{@code aws sqs} is the {@code aws} service.
+ * A running service is known by the service alone.
+ */
+ static String serviceOf(String alias) {
+ String s = alias.trim();
+ int space = s.indexOf(' ');
+ return space > 0 ? s.substring(0, space) : s;
+ }
+
+ /**
+ * Starts an infra service in the background via {@code camel infra run
<alias> --background}, where the alias may
+ * name the implementation too ({@code aws sqs}). The launch is monitored
like any other, so a failure surfaces
+ * through the failure log callback.
*/
void startInfra(String alias) throws IOException {
List<String> cmd = new ArrayList<>(LauncherHelper.getCamelCommand());
cmd.add("infra");
cmd.add("run");
- cmd.add(alias);
+ cmd.addAll(Arrays.asList(alias.trim().split("\\s+")));
cmd.add("--background");
Path outputFile = createSecureTempFile("camel-infra-", ".log");
outputFile.toFile().deleteOnExit();
@@ -342,7 +354,7 @@ class LaunchManager {
.filter(i -> i.alive)
.map(i -> i.alias)
.collect(Collectors.toSet());
- if (runningAliases.containsAll(deferredLaunch.requiredInfra)) {
+ if
(deferredLaunch.requiredInfra.stream().map(LaunchManager::serviceOf).allMatch(runningAliases::contains))
{
DeferredLaunch dl = deferredLaunch;
deferredLaunch = null;
dl.launchAction.run();
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/LaunchManagerInfraTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/LaunchManagerInfraTest.java
new file mode 100644
index 000000000000..88949e47f7db
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/LaunchManagerInfraTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.tui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * The infra services an example needs may name the implementation too ({@code
aws sqs}): it is started with both, and
+ * known as running by the service alone, as {@code camel infra run aws sqs}
runs as {@code aws}.
+ */
+class LaunchManagerInfraTest {
+
+ private final List<InfraInfo> running = new ArrayList<>();
+ private final LaunchManager lm = new LaunchManager(() -> running);
+
+ @Test
+ void theServiceOfAnEntryWithItsImplementation() {
+ assertThat(LaunchManager.serviceOf("aws sqs")).isEqualTo("aws");
+ assertThat(LaunchManager.serviceOf("kafka")).isEqualTo("kafka");
+ }
+
+ @Test
+ void aServiceWithItsImplementationIsMissingUntilItsServiceRuns() {
+ JsonObject example = new JsonObject();
+ example.put("infraServices", List.of("aws sqs", "kafka"));
+
+ assertThat(lm.findMissingInfraServices(example)).containsExactly("aws
sqs", "kafka");
+
+ running.add(infra("aws"));
+
assertThat(lm.findMissingInfraServices(example)).containsExactly("kafka");
+ }
+
+ private static InfraInfo infra(String alias) {
+ InfraInfo info = new InfraInfo();
+ info.alias = alias;
+ info.alive = true;
+ return info;
+ }
+}