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 6b7aff558d4a CAMEL-24727: camel-jbang-export - an export keeps to its
request (work dir, logging, system properties, repositories, leak) (#28688)
6b7aff558d4a is described below
commit 6b7aff558d4a881e0048830bf8f0c3209ab9ad44
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Oct 30 08:19:23 2026 +0100
CAMEL-24727: camel-jbang-export - an export keeps to its request (work dir,
logging, system properties, repositories, leak) (#28688)
Signed-off-by: Claus Ibsen <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../modules/ROOT/pages/camel-jbang-projects.adoc | 16 +++
.../camel/dsl/jbang/core/commands/Export.java | 1 +
.../dsl/jbang/core/commands/ExportBaseCommand.java | 19 +++
.../jbang/core/commands/MavenResolverMixin.java | 11 +-
.../commands/QuarkusExtensionRegistryMixin.java | 20 ++-
.../jbang/core/commands/QuarkusPlatformMixin.java | 5 +-
.../apache/camel/dsl/jbang/core/commands/Run.java | 62 ++++----
.../jbang/core/commands/RunDependencyResolver.java | 1 +
.../dsl/jbang/core/commands/ExportApiTest.java | 129 +++++++++++++++++
.../camel/dsl/jbang/export/CamelMainExporter.java | 14 +-
.../camel/dsl/jbang/export/DependencyResolver.java | 5 +-
.../camel/dsl/jbang/export/ExportRequest.java | 10 ++
.../apache/camel/dsl/jbang/export/Exporter.java | 52 +++----
.../camel/dsl/jbang/export/ProjectExporter.java | 18 +--
.../camel/dsl/jbang/export/QuarkusExporter.java | 10 +-
.../camel/dsl/jbang/export/SpringBootExporter.java | 11 +-
.../jbang/export/catalog/KameletCatalogHelper.java | 7 +-
.../dsl/jbang/export/common/CatalogLoader.java | 26 +++-
.../dsl/jbang/export/common/MavenResolver.java | 27 +++-
.../export/common/MavenResolverMixinSpec.java | 8 ++
.../dsl/jbang/export/common/QuarkusPlatform.java | 14 +-
.../dsl/jbang/export/ProjectExporterTest.java | 158 ++++++++++++++++++---
.../java/org/apache/camel/main/KameletMain.java | 17 ++-
.../main/download/MavenDependencyDownloader.java | 16 +++
.../camel/main/download/TransactedDownloader.java | 9 +-
.../camel/tooling/maven/MavenDownloader.java | 29 ++++
.../camel/tooling/maven/MavenDownloaderImpl.java | 30 ++--
.../tooling/maven/MavenDownloaderImplTest.java | 35 +++++
28 files changed, 622 insertions(+), 138 deletions(-)
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc
index 1503455cf940..0646f15651bd 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc
@@ -212,6 +212,22 @@ int exit = new ProjectExporter(context).export(request);
To find the dependencies of the integration, the export boots it silently, the
same way as `camel run` does.
This is done by a `DependencyResolver`, which `camel-jbang-core` provides, so
it must be on the classpath as well (or you set your own resolver on the
`ExportContext`).
+The export keeps to the request and leaves the JVM as it is, so an application
can export many times, and several exports can run at the same time:
+
+* The run settings of the silent boot and the project being built are kept in
the work directory of the request (`request.workDir`).
+It is the `.camel-jbang` directory of the current directory by default, as
with `camel export`, and it is deleted and created again on each export: give
each export that runs at the same time a directory of its own.
+* The export does not configure logging: it logs as the application has
configured.
+* The export reads no system properties for its options.
+`camel export` sets what its system properties configure (such as
`camel.jbang.camelSpringBootVersion` or `camel.jbang.repos`) on the request
before it exports.
+* The extra default Maven repositories are an option of the request, used for
the downloads and in the `pom.xml` of the project.
+When they are not set, they are the ones of the `camel.extra.repos` system
property, as with the other commands:
++
+[source,java]
+----
+// download from, and add to the pom.xml, this repository (and none of
camel.extra.repos)
+request.mavenResolver = new MavenResolver(true, null, false,
"acme=https://repo.acme.com/maven2");
+----
+
== Gathering list of dependencies
List the Camel components and JARs required to run your integration:
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Export.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Export.java
index 91d04b62291e..ba4e50aedd12 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Export.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Export.java
@@ -56,6 +56,7 @@ public class Export extends ExportBaseCommand {
ExportRequest request = toRequest();
ProjectExporter exporter = new ProjectExporter(exportContext());
Path baseDir = exporter.prepare(request);
+ applySystemProperties(request);
// the options from application.properties, the profile and the system
properties
fromRequest(request);
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 d7bfd1f8a58f..58fa300b7443 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
@@ -40,6 +40,7 @@ import org.apache.camel.dsl.jbang.export.ProjectExporter;
import org.apache.camel.dsl.jbang.export.common.HawtioVersion;
import org.apache.camel.dsl.jbang.export.common.MavenResolver;
import org.apache.camel.dsl.jbang.export.common.Printer;
+import org.apache.camel.dsl.jbang.export.common.PropertyResolver;
import org.apache.camel.dsl.jbang.export.common.QuarkusPlatform;
import org.apache.camel.dsl.jbang.export.common.RuntimeType;
import org.apache.camel.dsl.jbang.export.common.RuntimeUtil;
@@ -382,6 +383,7 @@ public abstract class ExportBaseCommand extends
CamelCommand {
protected Integer exportRuntime(Path exportBaseDir) throws Exception {
ExportRequest request = toRequest();
request.exportBaseDir = exportBaseDir;
+ applySystemProperties(request);
Exporter exporter = new
ProjectExporter(exportContext()).createExporter(request);
if (exporter == null) {
printer().printErr("Unknown runtime: " + runtime);
@@ -390,6 +392,23 @@ public abstract class ExportBaseCommand extends
CamelCommand {
return exporter.export();
}
+ /**
+ * Sets what the system properties of the CLI configure on the request, as
the export engine reads no system
+ * properties: the Quarkus platform, the Camel Spring Boot version, and
the repositories (camel.jbang.repos) when
+ * none are configured. The extra default repositories of the
camel.extra.repos system property are left to the
+ * Maven downloader.
+ */
+ static void applySystemProperties(ExportRequest request) {
+ request.quarkusPlatform = QuarkusPlatform.of(System.getProperties(),
request.quarkusPlatform);
+ request.camelSpringBootVersion
+ =
PropertyResolver.fromSystemProperty(CAMEL_SPRING_BOOT_VERSION, () ->
request.camelSpringBootVersion);
+ MavenResolver resolver = request.mavenResolver;
+ String repos = System.getProperty(REPOS);
+ if (resolver.repos() == null && repos != null) {
+ request.mavenResolver = new MavenResolver(resolver.download(),
repos, resolver.fresh(), resolver.extraRepos());
+ }
+ }
+
protected void addDependencies(String... deps) {
var depsArray = Optional.ofNullable(deps).orElse(new String[0]);
dependencies.addAll(Arrays.asList(depsArray));
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/MavenResolverMixin.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/MavenResolverMixin.java
index 864e21a187ac..fc42097e5ff1 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/MavenResolverMixin.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/MavenResolverMixin.java
@@ -43,6 +43,9 @@ public class MavenResolverMixin implements
MavenResolverMixinSpec {
defaultValue = "false")
boolean fresh;
+ // not an option: set by an export through the Java API, else the
camel.extra.repos system property is in use
+ String extraRepos;
+
private volatile MavenResolver resolver;
private final Object resolverLock = new Object();
@@ -52,6 +55,7 @@ public class MavenResolverMixin implements
MavenResolverMixinSpec {
=
Boolean.parseBoolean(props.getProperty(CamelJBangConstants.DOWNLOAD,
String.valueOf(fallback.download())));
result.repos = props.getProperty(CamelJBangConstants.REPOS,
fallback.repos());
result.fresh =
Boolean.parseBoolean(props.getProperty(CamelJBangConstants.FRESH,
String.valueOf(fallback.fresh())));
+ result.extraRepos = fallback.extraRepos();
return result;
}
@@ -80,7 +84,7 @@ public class MavenResolverMixin implements
MavenResolverMixinSpec {
if ((r = resolver) == null) {
synchronized (resolverLock) {
if ((r = resolver) == null) {
- r = new MavenResolver(download, repos, fresh);
+ r = new MavenResolver(download, repos, fresh, extraRepos);
resolver = r;
}
}
@@ -92,6 +96,11 @@ public class MavenResolverMixin implements
MavenResolverMixinSpec {
return fresh;
}
+ @Override
+ public String extraRepos() {
+ return extraRepos;
+ }
+
/**
* Put {@link #download}, {@link #repos} and {@link fresh} to the given
{@link Properties}.
*
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/QuarkusExtensionRegistryMixin.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/QuarkusExtensionRegistryMixin.java
index 981f0714bf02..2db7cd31083b 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/QuarkusExtensionRegistryMixin.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/QuarkusExtensionRegistryMixin.java
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.jbang.core.commands;
import java.nio.file.Path;
+import org.apache.camel.dsl.jbang.export.common.QuarkusHelper;
import org.apache.camel.dsl.jbang.export.common.QuarkusPlatform;
import picocli.CommandLine;
@@ -42,7 +43,24 @@ public class QuarkusExtensionRegistryMixin {
}
public String quarkusExtensionRegistryBaseUri() {
- return
QuarkusPlatform.resolveExtensionRegistryBaseUri(quarkusExtensionRegistryBaseUri);
+ return
resolveExtensionRegistryBaseUri(quarkusExtensionRegistryBaseUri);
+ }
+
+ /**
+ * The base URI of the Quarkus Extension Registry: the configured one,
else the one of the
+ * camel.jbang.quarkus.platform.url system property (without its
"/client/platforms" suffix), else the default
+ * registry; without a trailing slash.
+ */
+ public static String resolveExtensionRegistryBaseUri(String
quarkusExtensionRegistryBaseUri) {
+ String result = quarkusExtensionRegistryBaseUri;
+ if (result == null) {
+ result =
System.getProperty(QuarkusHelper.QUARKUS_PLATFORM_URL_PROPERTY);
+ final String suffix = "/client/platforms";
+ if (result != null && result.endsWith(suffix)) {
+ result = result.substring(0, result.length() -
suffix.length());
+ }
+ }
+ return QuarkusPlatform.extensionRegistryBaseUri(result);
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/QuarkusPlatformMixin.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/QuarkusPlatformMixin.java
index ad0abf76ab53..6a8a50b1efd8 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/QuarkusPlatformMixin.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/QuarkusPlatformMixin.java
@@ -68,10 +68,11 @@ public class QuarkusPlatformMixin extends
QuarkusExtensionRegistryMixin implemen
}
/**
- * @return the {@link QuarkusPlatform} with the options of this mixin
+ * @return the {@link QuarkusPlatform} with the options of this mixin, and
the base URI of the Quarkus Extension
+ * Registry of the camel.jbang.quarkus.platform.url system
property when it is not configured
*/
public QuarkusPlatform toPlatform() {
- return new QuarkusPlatform(quarkusGroupId, quarkusVersion,
quarkusExtensionRegistryBaseUri);
+ return new QuarkusPlatform(quarkusGroupId, quarkusVersion,
quarkusExtensionRegistryBaseUri());
}
/**
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 f140bdbe1f6c..d6fc24f850c4 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
@@ -77,9 +77,7 @@ import org.apache.camel.dsl.jbang.export.common.ExportHelper;
import org.apache.camel.dsl.jbang.export.common.GenAiDependencyHelper;
import org.apache.camel.dsl.jbang.export.common.PluginExporter;
import org.apache.camel.dsl.jbang.export.common.Printer;
-import org.apache.camel.dsl.jbang.export.common.PropertyResolver;
import
org.apache.camel.dsl.jbang.export.common.QuarkusHelper.QuarkusPlatformBom;
-import org.apache.camel.dsl.jbang.export.common.QuarkusPlatform;
import org.apache.camel.dsl.jbang.export.common.RuntimeType;
import org.apache.camel.dsl.jbang.export.common.RuntimeUtil;
import org.apache.camel.dsl.jbang.export.common.Source;
@@ -90,6 +88,7 @@ import org.apache.camel.dsl.jbang.export.common.VersionHelper;
import org.apache.camel.main.BaseMainSupport;
import org.apache.camel.main.KameletMain;
import org.apache.camel.main.MainListenerSupport;
+import org.apache.camel.main.SimpleMainShutdownStrategy;
import org.apache.camel.main.download.DownloadListener;
import org.apache.camel.main.util.SuggestSimilarHelper;
import org.apache.camel.spi.BacklogDebugger;
@@ -165,8 +164,8 @@ public class Run extends CamelCommand {
private static final Set<String> ACCEPTED_YAML_ROOT_ELEMENTS
= new HashSet<>(Arrays.asList(ACCEPTED_YAML_ROOT_ELEMENT_NAMES));
- private static final String OPENAPI_GENERATED_FILE =
CommandLineHelper.CAMEL_JBANG_WORK_DIR + "/generated-openapi.yaml";
- private static final String CLIPBOARD_GENERATED_FILE =
CommandLineHelper.CAMEL_JBANG_WORK_DIR + "/generated-clipboard";
+ private static final String OPENAPI_GENERATED_FILE =
"generated-openapi.yaml";
+ private static final String CLIPBOARD_GENERATED_FILE =
"generated-clipboard";
// the same layout as the camel-jbang console logging, so the log file
reads the same for every runtime
static final String QUARKUS_LOG_FILE_FORMAT
= "%d{yyyy-MM-dd HH:mm:ss.SSS} %5p %i --- [%15.15t] %-40.40c{3.} :
%s%e%n";
@@ -181,6 +180,8 @@ public class Run extends CamelCommand {
public boolean exportRun;
protected Path exportBaseDir;
+ // the working directory, which is deleted and created again when running
+ Path workDir = CommandLineHelper.getWorkDir();
boolean scriptRun;
boolean transformRun;
boolean transformMessageRun;
@@ -861,13 +862,12 @@ public class Run extends CamelCommand {
}
}
- Path work = CommandLineHelper.getWorkDir();
- removeDir(work);
- if (!Files.exists(work)) {
+ removeDir(workDir);
+ if (!Files.exists(workDir)) {
try {
- Files.createDirectories(work);
+ Files.createDirectories(workDir);
} catch (IOException e) {
- printer().printWarn("Failed to create working directory: " +
work.toAbsolutePath());
+ printer().printWarn("Failed to create working directory: " +
workDir.toAbsolutePath());
}
}
@@ -947,10 +947,16 @@ public class Run extends CamelCommand {
}
final KameletMain main = createMainInstance();
+ if (exportRun) {
+ // the silent run of an export boots and stops in this JVM, which
may run many exports (camel export
+ // through the Java API): the default strategy adds a JVM shutdown
hook that keeps the run until the JVM exits
+ main.setShutdownStrategy(new SimpleMainShutdownStrategy());
+ }
main.setProfile(profile);
if (mavenResolver.repos() != null && !mavenResolver.repos().isBlank())
{
main.setRepositories(mavenResolver.repos());
}
+ main.setExtraRepositories(mavenResolver.extraRepos());
main.setDownload(mavenResolver.download());
main.setPackageScanJars(packageScanJars);
main.setFresh(mavenResolver.fresh());
@@ -1018,8 +1024,7 @@ public class Run extends CamelCommand {
if (prompt) {
writeSetting(main, profileProperties, PROMPT, "true");
}
- writeSetting(main, profileProperties, COMPILE_WORK_DIR,
- Paths.get(CommandLineHelper.CAMEL_JBANG_WORK_DIR,
"compile").toString());
+ writeSetting(main, profileProperties, COMPILE_WORK_DIR,
workDir.resolve("compile").toString());
if (gav != null) {
writeSetting(main, profileProperties, GAV, gav);
@@ -1142,7 +1147,7 @@ public class Run extends CamelCommand {
// include generated openapi to files to run
if (openapi != null) {
- files.add(OPENAPI_GENERATED_FILE);
+ files.add(openApiGeneratedFile());
}
// if we only run pom.xml then auto discover from the Maven based
project
@@ -1736,7 +1741,7 @@ public class Run extends CamelCommand {
eq.javaLiveReload = this.dev;
eq.symbolicLink = this.dev;
eq.mavenWrapper = true;
- eq.quarkusPlatform = QuarkusPlatform.of(System.getProperties(),
this.quarkusPlatform);
+ eq.quarkusPlatform = this.quarkusPlatform.toPlatform();
eq.javaVersion = javaVersion;
eq.camelVersion = this.camelVersion;
eq.javaVersion = this.javaVersion;
@@ -1779,6 +1784,7 @@ public class Run extends CamelCommand {
printer().println("Running using Quarkus (preparing and downloading
files)");
// run export
+ ExportBaseCommand.applySystemProperties(eq);
int exit = new QuarkusExporter(eq, CliExportContext.create(getMain(),
getMain().getOut(), eq.yes)).export();
if (exit != 0) {
return exit;
@@ -1940,6 +1946,7 @@ public class Run extends CamelCommand {
printer().println("Running using Camel Main (preparing and downloading
files)");
// run export
+ ExportBaseCommand.applySystemProperties(eq);
int exit = new CamelMainExporter(eq,
CliExportContext.create(getMain(), getMain().getOut(), eq.yes)).export();
if (exit != 0) {
return exit;
@@ -2547,8 +2554,7 @@ public class Run extends CamelCommand {
eq.springBootVersion = this.springBootVersion;
eq.javaVersion = this.javaVersion;
eq.camelVersion = this.camelVersion;
- eq.camelSpringBootVersion =
PropertyResolver.fromSystemProperty(CAMEL_SPRING_BOOT_VERSION,
- () -> this.camelSpringBootVersion != null ?
this.camelSpringBootVersion : this.camelVersion);
+ eq.camelSpringBootVersion = this.camelSpringBootVersion != null ?
this.camelSpringBootVersion : this.camelVersion;
eq.javaVersion = this.javaVersion;
eq.kameletsVersion = this.kameletsVersion;
eq.exportDir = runDirPath.toString();
@@ -2593,6 +2599,7 @@ public class Run extends CamelCommand {
printer().println("Running using Spring Boot (preparing and
downloading files)");
// run export
+ ExportBaseCommand.applySystemProperties(eq);
int exit = new SpringBootExporter(eq,
CliExportContext.create(getMain(), getMain().getOut(), eq.yes)).export();
if (exit != 0) {
return exit;
@@ -3429,7 +3436,7 @@ public class Run extends CamelCommand {
Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
Object t = c.getData(DataFlavor.stringFlavor);
if (t != null) {
- String fn = CLIPBOARD_GENERATED_FILE + "." + ext;
+ String fn = workDir + "/" + CLIPBOARD_GENERATED_FILE + "." + ext;
if ("java".equals(ext)) {
String fqn = determineClassName(t.toString());
if (fqn == null) {
@@ -3485,6 +3492,8 @@ public class Run extends CamelCommand {
}
private void configureLogging(Path baseDir, Properties profileProperties)
throws Exception {
+ // the silent run of an export only writes the logging settings: it
runs inside the export, which logs as its
+ // caller has configured (camel export configures logging before it
exports)
if (loggingOptions.logging) {
// allow to configure individual logging levels in
application.properties
Properties prop = loadProfileProperties(baseDir);
@@ -3522,13 +3531,15 @@ public class Run extends CamelCommand {
}
}
}
- RuntimeUtil.configureLog(loggingOptions.loggingLevel,
loggingOptions.loggingColor,
- loggingOptions.loggingJson, scriptRun, false,
loggingOptions.loggingConfigPath,
- loggingOptions.loggingCategory, mcpStdioMode);
+ if (!exportRun) {
+ RuntimeUtil.configureLog(loggingOptions.loggingLevel,
loggingOptions.loggingColor,
+ loggingOptions.loggingJson, scriptRun, false,
loggingOptions.loggingConfigPath,
+ loggingOptions.loggingCategory, mcpStdioMode);
+ }
writeSettings("loggingLevel", loggingOptions.loggingLevel);
writeSettings("loggingColor", loggingOptions.loggingColor ? "true"
: "false");
writeSettings("loggingJson", loggingOptions.loggingJson ? "true" :
"false");
- if (!scriptRun) {
+ if (!scriptRun && !exportRun) {
// remember log file
String name = RuntimeUtil.getPid() + ".log";
final Path logDir = CommandLineHelper.getCamelDir();
@@ -3544,7 +3555,6 @@ public class Run extends CamelCommand {
}
} else {
if (exportRun) {
- RuntimeUtil.configureLog(loggingOptions.loggingLevel, false,
false, false, true, null, null);
writeSettings("loggingLevel", loggingOptions.loggingLevel);
} else {
RuntimeUtil.configureLog("off", false, false, false, false,
null, null);
@@ -3562,7 +3572,7 @@ public class Run extends CamelCommand {
Map<String, Object> model = new HashMap<>();
model.put("Spec", filePath.toString());
String content = TemplateHelper.processTemplate("rest-dsl.yaml.ftl",
model);
- Files.writeString(Paths.get(OPENAPI_GENERATED_FILE), content);
+ Files.writeString(Paths.get(openApiGeneratedFile()), content);
// we need to include the spec on the classpath
files.add(openapi);
@@ -3615,7 +3625,7 @@ public class Run extends CamelCommand {
// flatten file
name = FileUtil.stripPath(name);
- if (OPENAPI_GENERATED_FILE.equals(name)) {
+ if (openApiGeneratedFile().equals(name)) {
return false;
}
if ("pom.xml".equalsIgnoreCase(name)) {
@@ -3706,6 +3716,10 @@ public class Run extends CamelCommand {
return ExportHelper.walkResourceDir(dirPath);
}
+ private String openApiGeneratedFile() {
+ return workDir + "/" + OPENAPI_GENERATED_FILE;
+ }
+
private void writeSettings(String key, String value) {
try {
// use java.util.Properties to ensure the value is escaped
correctly
@@ -3714,7 +3728,7 @@ public class Run extends CamelCommand {
StringWriter sw = new StringWriter();
prop.store(sw, null);
- Path runSettingsPath =
CommandLineHelper.getWorkDir().resolve(RUN_SETTINGS_FILE);
+ Path runSettingsPath = workDir.resolve(RUN_SETTINGS_FILE);
StringBuilder content = new StringBuilder();
String[] lines = sw.toString().split(System.lineSeparator());
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/RunDependencyResolver.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/RunDependencyResolver.java
index 28adbc059961..a9756284e456 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/RunDependencyResolver.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/RunDependencyResolver.java
@@ -42,6 +42,7 @@ public class RunDependencyResolver implements
DependencyResolver {
Run run = new Run(main);
// need to declare the profile to use for run
run.exportBaseDir = request.exportBaseDir;
+ run.workDir = request.workDir;
run.dependencies = request.dependencies;
run.files = request.files;
run.name = request.name;
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ExportApiTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ExportApiTest.java
new file mode 100644
index 000000000000..a2b7e4140914
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ExportApiTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.lang.ref.WeakReference;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.dsl.jbang.export.ExportContext;
+import org.apache.camel.dsl.jbang.export.ExportRequest;
+import org.apache.camel.dsl.jbang.export.ProjectExporter;
+import org.apache.camel.dsl.jbang.export.common.ExportHelper;
+import org.apache.camel.dsl.jbang.export.common.RuntimeType;
+import org.apache.camel.dsl.jbang.export.common.StringPrinter;
+import org.apache.camel.spi.CamelContextTracker;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+/**
+ * Exports through the Java API with the dependency resolver of
camel-jbang-core (the silent run), as an application
+ * that embeds the export does: the export keeps to its work directory, leaves
the logging and the system properties of
+ * the JVM as they are, and releases its silent run.
+ */
+class ExportApiTest {
+
+ @TempDir
+ Path dir;
+
+ private Path route;
+
+ @BeforeEach
+ void copyRoute() throws Exception {
+ route = dir.resolve("hello.camel.yaml");
+ Files.copy(Path.of("src/test/resources/hello.camel.yaml"), route);
+ }
+
+ @Test
+ void exportsWithoutSideEffects() throws Exception {
+ ExportRequest request = request(0);
+
+ Configuration logging =
LoggerContext.getContext(false).getConfiguration();
+ Map<Object, Object> properties = new HashMap<>(System.getProperties());
+
+ StringPrinter printer = new StringPrinter();
+ int exit = new ProjectExporter(new
ExportContext().withPrinter(printer)).export(request);
+
+ assertThat(exit).as(printer.getOutput()).isZero();
+
assertThat(Files.readString(dir.resolve("export0/pom.xml"))).contains("<artifactId>camel-timer</artifactId>");
+ // the silent run writes its settings to the work directory of the
request (the export tests in
+ // camel-jbang-export check that the current directory is left alone,
which the forks of this module share)
+
assertThat(request.workDir.resolve(ExportHelper.RUN_SETTINGS_FILE)).content()
+ .contains("camel.main.routesIncludePattern=",
"hello.camel.yaml");
+
+
assertThat(LoggerContext.getContext(false).getConfiguration()).isSameAs(logging);
+ assertThat(new
HashMap<>(System.getProperties())).isEqualTo(properties);
+ }
+
+ @Test
+ void repeatedExportsReleaseTheirSilentRuns() throws Exception {
+ // the first export starts what stays for the JVM, such as the shared
pools of the Maven resolver
+ export(0);
+ Set<Thread> threads = Thread.getAllStackTraces().keySet();
+
+ List<WeakReference<CamelContext>> contexts = new
CopyOnWriteArrayList<>();
+ try (CamelContextTracker tracker = new CamelContextTracker() {
+ @Override
+ public void contextCreated(CamelContext camelContext) {
+ contexts.add(new WeakReference<>(camelContext));
+ }
+ }) {
+ tracker.open();
+ for (int i = 1; i <= 20; i++) {
+ export(i);
+ }
+ }
+
+ assertThat(contexts).hasSizeGreaterThanOrEqualTo(20);
+ // nothing holds on to the CamelContext of a silent run once its
export is done
+ await().atMost(20, TimeUnit.SECONDS).untilAsserted(() -> {
+ System.gc();
+ assertThat(contexts).allSatisfy(ref ->
assertThat(ref.get()).isNull());
+ });
+ assertThat(Thread.getAllStackTraces().keySet()).allSatisfy(t ->
assertThat(threads).contains(t));
+ }
+
+ private ExportRequest request(int i) {
+ ExportRequest request = new ExportRequest();
+ request.files.add(route.toString());
+ request.runtime = RuntimeType.main;
+ request.exportDir = dir.resolve("export" + i).toString();
+ request.workDir = dir.resolve("work" + i);
+ request.mavenWrapper = false;
+ request.quiet = true;
+ return request;
+ }
+
+ private void export(int i) throws Exception {
+ StringPrinter printer = new StringPrinter();
+ assertThat(new ProjectExporter(new
ExportContext().withPrinter(printer)).export(request(i)))
+ .as(printer.getOutput()).isZero();
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/CamelMainExporter.java
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/CamelMainExporter.java
index 6d4e1faed570..e2c964c235c8 100644
---
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/CamelMainExporter.java
+++
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/CamelMainExporter.java
@@ -31,7 +31,6 @@ import java.util.Set;
import org.apache.camel.catalog.CamelCatalog;
import org.apache.camel.catalog.DefaultCamelCatalog;
-import org.apache.camel.dsl.jbang.export.common.CamelHome;
import org.apache.camel.dsl.jbang.export.common.CamelJBangConstants;
import org.apache.camel.dsl.jbang.export.common.ExportHelper;
import org.apache.camel.dsl.jbang.export.common.PathUtils;
@@ -49,8 +48,6 @@ import static
org.apache.camel.dsl.jbang.export.common.ExportHelper.exportPackag
*/
public class CamelMainExporter extends Exporter {
- protected static final String GROOVY_COMPILE_DIR =
CamelHome.CAMEL_JBANG_WORK_DIR + "/compile/groovy";
-
public CamelMainExporter(ExportRequest request, ExportContext context) {
super(request, context);
if (pomTemplateName == null) {
@@ -77,7 +74,7 @@ public class CamelMainExporter extends Exporter {
String[] ids = gav.split(":");
// the settings file has information what to export
- Path settings =
CamelHome.getWorkDir().resolve(ExportHelper.RUN_SETTINGS_FILE);
+ Path settings = runSettingsFile();
if (mavenResolver.fresh() || !files.isEmpty() ||
!Files.exists(settings)) {
// allow to automatic build
printer().println("Generating fresh run data");
@@ -95,7 +92,7 @@ public class CamelMainExporter extends Exporter {
Path profile = exportBaseDir.resolve("application.properties");
// use a temporary work dir
- Path buildDir = Path.of(BUILD_DIR);
+ Path buildDir = buildDir();
PathUtils.deleteDirectory(buildDir);
Files.createDirectories(buildDir);
@@ -198,10 +195,10 @@ public class CamelMainExporter extends Exporter {
copyCamelWrapper();
}
if (docker) {
- copyDockerFiles(BUILD_DIR);
+ copyDockerFiles(buildDir.toString());
}
String appJar = Paths.get("target", ids[1] + "-" + ids[2] +
".jar").toString();
- copyReadme(BUILD_DIR, appJar);
+ copyReadme(buildDir.toString(), appJar);
if (cleanExportDir || !isCurrentDirOrParent(exportDir)) {
// cleaning current dir can be a bit dangerous so only clean if
explicit enabled
// otherwise always clean export-dir to avoid stale data
@@ -392,7 +389,8 @@ public class CamelMainExporter extends Exporter {
protected void copyGroovyPrecompiled(Path srcResourcesDir) throws
Exception {
// are there any pre-compiled groovy code
- File gc = new File(GROOVY_COMPILE_DIR);
+ // the silent run compiles the groovy code into the work directory
+ File gc = workDir.resolve("compile/groovy").toFile();
if (gc.exists() && gc.isDirectory()) {
File[] files = gc.listFiles();
if (files != null) {
diff --git
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/DependencyResolver.java
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/DependencyResolver.java
index f81f9602fc1c..77891dd3eac3 100644
---
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/DependencyResolver.java
+++
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/DependencyResolver.java
@@ -16,7 +16,6 @@
*/
package org.apache.camel.dsl.jbang.export;
-import org.apache.camel.dsl.jbang.export.common.CamelHome;
import org.apache.camel.dsl.jbang.export.common.ExportHelper;
/**
@@ -24,8 +23,8 @@ import org.apache.camel.dsl.jbang.export.common.ExportHelper;
* <p>
* The export boots the integration silently to learn what it uses: the
components, languages, data formats and other
* dependencies, and how the source files are classified. The resolver writes
these as run settings to
- * {@link ExportHelper#RUN_SETTINGS_FILE} in the work directory ({@link
CamelHome#getWorkDir()}), which the exporter
- * then reads.
+ * {@link ExportHelper#RUN_SETTINGS_FILE} in the work directory of the request
({@link ExportRequest#workDir}), which
+ * the exporter then reads.
* <p>
* camel-jbang-core provides the implementation that boots the integration the
same way as camel run does, and registers
* it with the {@link java.util.ServiceLoader}.
diff --git
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/ExportRequest.java
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/ExportRequest.java
index bf30c49be942..93294008077e 100644
---
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/ExportRequest.java
+++
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/ExportRequest.java
@@ -20,6 +20,7 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
+import org.apache.camel.dsl.jbang.export.common.CamelHome;
import org.apache.camel.dsl.jbang.export.common.MavenResolver;
import org.apache.camel.dsl.jbang.export.common.QuarkusPlatform;
import org.apache.camel.dsl.jbang.export.common.RuntimeType;
@@ -201,6 +202,14 @@ public class ExportRequest {
*/
public String exportDir = ".";
+ /**
+ * The working directory of the export: the run settings of the dependency
resolution and the project being built
+ * are kept there, and the directory is deleted and created again on each
export. The default is the .camel-jbang
+ * directory in the current directory, as with camel export; use a
directory of its own for each export that runs at
+ * the same time.
+ */
+ public Path workDir = CamelHome.getWorkDir();
+
/**
* If exporting to current directory (default) then all existing files are
preserved. Enabling this option will
* force cleaning current directory including all sub dirs (use this with
care).
@@ -352,6 +361,7 @@ public class ExportRequest {
this.consoleForRun = other.consoleForRun;
this.console = other.console;
this.exportDir = other.exportDir;
+ this.workDir = other.workDir;
this.cleanExportDir = other.cleanExportDir;
this.yes = other.yes;
this.loggingLevel = other.loggingLevel;
diff --git
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/Exporter.java
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/Exporter.java
index 4c39800acaa0..b00a60127630 100644
---
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/Exporter.java
+++
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/Exporter.java
@@ -52,7 +52,6 @@ import java.util.stream.Stream;
import freemarker.template.TemplateNotFoundException;
import org.apache.camel.dsl.jbang.export.catalog.KameletCatalogHelper;
-import org.apache.camel.dsl.jbang.export.common.CamelHome;
import org.apache.camel.dsl.jbang.export.common.ExportHelper;
import org.apache.camel.dsl.jbang.export.common.GenAiDependencyHelper;
import org.apache.camel.dsl.jbang.export.common.PluginExporter;
@@ -78,8 +77,6 @@ import static
org.apache.camel.dsl.jbang.export.common.CamelJBangConstants.*;
*/
public abstract class Exporter extends ExportRequest {
- protected static final String BUILD_DIR = CamelHome.CAMEL_JBANG_WORK_DIR +
"/work";
-
protected static final String[] SETTINGS_PROP_SOURCE_KEYS = new String[] {
"camel.main.routesIncludePattern",
"camel.component.properties.location",
@@ -96,13 +93,10 @@ public abstract class Exporter extends ExportRequest {
private static final Pattern PACKAGE_PATTERN = Pattern.compile(
"^\\s*package\\s+([a-zA-Z][.\\w]*)\\s*;.*$", Pattern.MULTILINE);
- private static final String EXTRA_REPOS_PROPERTY = "camel.extra.repos";
- private static final String EXTRA_REPOS_DEFAULT_VALUE_PROPERTY =
"camel.default.extra.repos.default.value";
-
private static final Set<String> EXCLUDED_GROUP_IDS =
Set.of("org.fusesource.jansi", "org.apache.logging.log4j");
private Printer quietPrinter;
- // Map to store deferred symbolic links: target path in BUILD_DIR →
original source path
+ // Map to store deferred symbolic links: target path in the build dir →
original source path
private Map<Path, Path> deferredSymlinks = new HashMap<>();
protected final ExportContext exportContext;
@@ -112,6 +106,20 @@ public abstract class Exporter extends ExportRequest {
this.exportContext = context;
}
+ /**
+ * The directory in the work directory where the project is built, before
it is copied to the export directory.
+ */
+ protected Path buildDir() {
+ return workDir.resolve("work");
+ }
+
+ /**
+ * The run settings in the work directory, which the dependency resolver
writes.
+ */
+ protected Path runSettingsFile() {
+ return workDir.resolve(ExportHelper.RUN_SETTINGS_FILE);
+ }
+
/**
* Builds a list of repository data maps from a comma-separated repos
string, for use with FreeMarker templates.
*/
@@ -974,11 +982,11 @@ public abstract class Exporter extends ExportRequest {
}
protected void copyMavenWrapper() throws Exception {
- Path wrapperPath = Paths.get(BUILD_DIR, ".mvn/wrapper");
+ Path wrapperPath = buildDir().resolve(".mvn/wrapper");
Files.createDirectories(wrapperPath);
// copy files
- Path mvnwPath = Paths.get(BUILD_DIR, "mvnw");
- Path mvnwCmdPath = Paths.get(BUILD_DIR, "mvnw.cmd");
+ Path mvnwPath = buildDir().resolve("mvnw");
+ Path mvnwCmdPath = buildDir().resolve("mvnw.cmd");
Path wrapperJarPath = wrapperPath.resolve("maven-wrapper.jar");
Path wrapperPropsPath =
wrapperPath.resolve("maven-wrapper.properties");
@@ -1002,7 +1010,7 @@ public abstract class Exporter extends ExportRequest {
}
protected void copyCamelWrapper() throws Exception {
- Path camelDir = Paths.get(BUILD_DIR, ".camel");
+ Path camelDir = buildDir().resolve(".camel");
Files.createDirectories(camelDir);
// write camel-wrapper.properties with the export's camel version
@@ -1017,8 +1025,8 @@ public abstract class Exporter extends ExportRequest {
Files.writeString(camelDir.resolve("camel-wrapper.properties"),
content);
// copy wrapper scripts
- Path camelPath = Paths.get(BUILD_DIR, "camel");
- Path camelCmdPath = Paths.get(BUILD_DIR, "camel.cmd");
+ Path camelPath = buildDir().resolve("camel");
+ Path camelCmdPath = buildDir().resolve("camel.cmd");
try (InputStream is =
Exporter.class.getClassLoader().getResourceAsStream("camel-wrapper/camelw")) {
Files.copy(is, camelPath, StandardCopyOption.REPLACE_EXISTING);
}
@@ -1047,10 +1055,6 @@ public abstract class Exporter extends ExportRequest {
Set<String> answer = new LinkedHashSet<>();
String propRepositories = prop.getProperty(REPOS);
- if (propRepositories == null) {
- // fallback to system property
- propRepositories = System.getProperty(REPOS);
- }
if (propRepositories != null) {
Collections.addAll(answer, propRepositories.split("\\s*,\\s*"));
}
@@ -1073,9 +1077,9 @@ public abstract class Exporter extends ExportRequest {
Collections.addAll(answer,
this.mavenResolver.repos().split("\\s*,\\s*"));
}
- // include extra repos from system property
- String extraRepos = System.getProperty(EXTRA_REPOS_PROPERTY,
- System.getProperty(EXTRA_REPOS_DEFAULT_VALUE_PROPERTY));
+ // include the extra default repositories the downloads use (the
camel.extra.repos system property unless the
+ // request sets them)
+ String extraRepos = mavenResolver.downloader().getExtraRepos();
if (extraRepos != null && !extraRepos.isBlank()) {
for (String r : extraRepos.split("\\s*,\\s*")) {
if (!r.isBlank()) {
@@ -1246,7 +1250,7 @@ public abstract class Exporter extends ExportRequest {
/**
* Creates symbolic links for files that were deferred during the copy
phase. This method should be called after
- * copying BUILD_DIR to exportDir and before deleting BUILD_DIR.
+ * copying the build dir to exportDir and before deleting the build dir.
*
* @param buildDir the temporary build directory
* @param exportDir the final export directory
@@ -1374,7 +1378,7 @@ public abstract class Exporter extends ExportRequest {
protected void copyLocalLibDependencies(Set<String> deps) throws Exception
{
for (String d : deps) {
if (d.startsWith("lib:")) {
- Path libDirPath = Paths.get(BUILD_DIR, "lib");
+ Path libDirPath = buildDir().resolve("lib");
Files.createDirectories(libDirPath);
String n = d.substring(4);
Path sourcePath = Paths.get(n);
@@ -1387,7 +1391,7 @@ public abstract class Exporter extends ExportRequest {
protected void copyAgentDependencies(Set<String> deps) throws Exception {
for (String d : deps) {
if (d.startsWith("agent:")) {
- Path libDirPath = Paths.get(BUILD_DIR, "agent");
+ Path libDirPath = buildDir().resolve("agent");
Files.createDirectories(libDirPath);
String n = d.substring(6);
MavenGav gav = MavenGav.parseGav(n);
@@ -1401,7 +1405,7 @@ public abstract class Exporter extends ExportRequest {
List<MavenArtifact> artifacts =
mavenResolver.downloader().resolveArtifacts(
List.of(gav.toString()), Set.of(), true,
gav.getVersion().contains("SNAPSHOT"));
for (MavenArtifact artifact : artifacts) {
- Path target = Paths.get(BUILD_DIR, "agent",
artifact.getFile().getName());
+ Path target =
buildDir().resolve("agent").resolve(artifact.getFile().getName());
if (Files.exists(target) ||
EXCLUDED_GROUP_IDS.contains(artifact.getGav().getGroupId())) {
continue;
}
diff --git
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/ProjectExporter.java
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/ProjectExporter.java
index ae9a04623606..19eba96174b0 100644
---
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/ProjectExporter.java
+++
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/ProjectExporter.java
@@ -27,7 +27,6 @@ import
org.apache.camel.dsl.jbang.export.common.CamelJBangConstants;
import org.apache.camel.dsl.jbang.export.common.ExportHelper;
import org.apache.camel.dsl.jbang.export.common.MavenResolver;
import org.apache.camel.dsl.jbang.export.common.Printer;
-import org.apache.camel.dsl.jbang.export.common.PropertyResolver;
import org.apache.camel.dsl.jbang.export.common.QuarkusPlatform;
import org.apache.camel.dsl.jbang.export.common.RuntimeType;
import org.apache.camel.dsl.jbang.export.common.RuntimeUtil;
@@ -111,8 +110,9 @@ public class ProjectExporter {
/**
* Prepares the request: a single directory as the file to export is the
directory of the files to export, the
- * application.properties (and the one of the profile) of that directory
are included, and the options they (and the
- * system properties) configure are set on the request.
+ * application.properties (and the one of the profile) of that directory
are included, and the options they
+ * configure are set on the request. System properties are not read: the
caller sets what they configure on the
+ * request (camel export does so after the preparation).
*
* @param request the request, which is completed
* @return the base directory of the files to export
@@ -154,9 +154,6 @@ public class ProjectExporter {
doLoadAndInitProfileProperties(request,
baseDir.resolve("application-" + request.profile + ".properties"));
}
- // property overrides from system properties for supported properties
- overrideFromSystemProperties(request);
-
return baseDir;
}
@@ -296,13 +293,4 @@ public class ProjectExporter {
request.excludes =
RuntimeUtil.getCommaSeparatedPropertyAsList(props, EXCLUDES, request.excludes);
}
}
-
- /**
- * For supported fields prefer the values from system properties if they
are defined.
- */
- private static void overrideFromSystemProperties(ExportRequest request) {
- request.quarkusPlatform = QuarkusPlatform.of(System.getProperties(),
request.quarkusPlatform);
- request.camelSpringBootVersion
- =
PropertyResolver.fromSystemProperty(CAMEL_SPRING_BOOT_VERSION, () ->
request.camelSpringBootVersion);
- }
}
diff --git
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/QuarkusExporter.java
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/QuarkusExporter.java
index 5ae19d501748..a295d8d51452 100644
---
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/QuarkusExporter.java
+++
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/QuarkusExporter.java
@@ -46,7 +46,6 @@ import org.w3c.dom.Document;
import org.xml.sax.SAXException;
-import org.apache.camel.dsl.jbang.export.common.CamelHome;
import org.apache.camel.dsl.jbang.export.common.ExportHelper;
import org.apache.camel.dsl.jbang.export.common.PathUtils;
import org.apache.camel.dsl.jbang.export.common.QuarkusHelper;
@@ -100,7 +99,7 @@ public class QuarkusExporter extends Exporter {
Path profile = exportBaseDir.resolve("application.properties");
// the settings file has information what to export
- Path settings =
CamelHome.getWorkDir().resolve(ExportHelper.RUN_SETTINGS_FILE);
+ Path settings = runSettingsFile();
if (mavenResolver.fresh() || !files.isEmpty() ||
!Files.exists(settings)) {
// allow to automatic build
printer().println("Generating fresh run data");
@@ -115,7 +114,7 @@ public class QuarkusExporter extends Exporter {
printer().println("Exporting as Quarkus project to: " + exportDir);
// use a temporary work dir
- Path buildDir = Path.of(BUILD_DIR);
+ Path buildDir = buildDir();
PathUtils.deleteDirectory(buildDir);
Files.createDirectories(buildDir);
@@ -172,7 +171,7 @@ public class QuarkusExporter extends Exporter {
writeDevProfileProperties(srcResourcesDir, devProfile);
// copy docker files
if (docker) {
- copyDockerFiles(BUILD_DIR);
+ copyDockerFiles(buildDir.toString());
}
String appJar;
if ("fast-jar".equals(quarkusPackageType)) {
@@ -180,7 +179,7 @@ public class QuarkusExporter extends Exporter {
} else {
appJar = "target" + File.separator + ids[1] + "-" + ids[2] +
".jar";
}
- copyReadme(BUILD_DIR, appJar);
+ copyReadme(buildDir.toString(), appJar);
// gather dependencies
Set<String> deps = resolveDependencies(settings, profile);
// copy local lib JARs
@@ -295,7 +294,6 @@ public class QuarkusExporter extends Exporter {
fileName = StringHelper.after(fileName, ":");
}
fileName = FileUtil.stripPath(fileName);
- fileName = fileName.replace(CamelHome.CAMEL_JBANG_WORK_DIR + "/", "");
return fileName;
}
diff --git
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/SpringBootExporter.java
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/SpringBootExporter.java
index d0439d8ec325..1a354cf9209a 100644
---
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/SpringBootExporter.java
+++
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/SpringBootExporter.java
@@ -35,7 +35,6 @@ import java.util.Properties;
import java.util.Set;
import org.apache.camel.catalog.CamelCatalog;
-import org.apache.camel.dsl.jbang.export.common.CamelHome;
import org.apache.camel.dsl.jbang.export.common.CamelJBangConstants;
import org.apache.camel.dsl.jbang.export.common.CatalogLoader;
import org.apache.camel.dsl.jbang.export.common.ExportHelper;
@@ -86,7 +85,7 @@ public class SpringBootExporter extends Exporter {
Path profile = exportBaseDir.resolve("application.properties");
// the settings file has information what to export
- Path settings =
CamelHome.getWorkDir().resolve(ExportHelper.RUN_SETTINGS_FILE);
+ Path settings = runSettingsFile();
if (mavenResolver.fresh() || !files.isEmpty() ||
!Files.exists(settings)) {
// allow to automatic build
printer().println("Generating fresh run data");
@@ -101,7 +100,7 @@ public class SpringBootExporter extends Exporter {
printer().println("Exporting as Spring Boot project to: " + exportDir);
// use a temporary work dir
- Path buildDir = Path.of(BUILD_DIR);
+ Path buildDir = buildDir();
PathUtils.deleteDirectory(buildDir);
Files.createDirectories(buildDir);
@@ -195,10 +194,10 @@ public class SpringBootExporter extends Exporter {
copyCamelWrapper();
}
if (docker) {
- copyDockerFiles(BUILD_DIR);
+ copyDockerFiles(buildDir.toString());
}
String appJar = "target" + File.separator + ids[1] + "-" + ids[2] +
".jar";
- copyReadme(BUILD_DIR, appJar);
+ copyReadme(buildDir.toString(), appJar);
if (cleanExportDir || !isCurrentDirOrParent(exportDir)) {
// cleaning current dir can be a bit dangerous so only clean if
explicit enabled
// otherwise always clean export-dir to avoid stale data
@@ -227,7 +226,7 @@ public class SpringBootExporter extends Exporter {
? camelSpringBootVersion : (camelVersion == null ?
RuntimeType.main.version() : camelVersion);
String repos = getMavenRepositories(settings, prop, sbVersion);
- CamelCatalog catalog = CatalogLoader.loadSpringBootCatalog(repos,
sbVersion, mavenResolver.download());
+ CamelCatalog catalog = CatalogLoader.loadSpringBootCatalog(repos,
sbVersion, sbVersion, null, mavenResolver.download());
if (ObjectHelper.isEmpty(camelVersion)) {
camelVersion = catalog.getLoadedVersion();
}
diff --git
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/catalog/KameletCatalogHelper.java
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/catalog/KameletCatalogHelper.java
index 07cafb09bcb9..f94fe9c35e0f 100644
---
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/catalog/KameletCatalogHelper.java
+++
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/catalog/KameletCatalogHelper.java
@@ -187,7 +187,12 @@ public final class KameletCatalogHelper {
Map<String, Object> answer = (Map<String, Object>)
ObjectHelper.invokeMethod(m, catalog);
if (release && answer != null) {
answer = Collections.unmodifiableMap(answer);
- KAMELETS.put(key, answer);
+ // another thread may have loaded it at the same time (such as
exports that run in parallel): the
+ // callers share the first one
+ Map<String, Object> existing = KAMELETS.putIfAbsent(key,
answer);
+ if (existing != null) {
+ answer = existing;
+ }
}
return answer;
} finally {
diff --git
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/CatalogLoader.java
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/CatalogLoader.java
index a0ec03b078ee..489b6be00c01 100644
---
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/CatalogLoader.java
+++
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/CatalogLoader.java
@@ -112,6 +112,25 @@ public final class CatalogLoader {
public static CamelCatalog loadSpringBootCatalog(
String repos, String version, String springBootGroupId, boolean
download)
throws Exception {
+ final String camelVersion = version;
+ return loadSpringBootCatalog(repos, version,
+
PropertyResolver.fromSystemProperty(CamelJBangConstants.CAMEL_SPRING_BOOT_VERSION,
() -> camelVersion),
+ springBootGroupId, download);
+ }
+
+ /**
+ * Loads the catalog of Camel Spring Boot.
+ *
+ * @param repos additional Maven repositories
+ * @param version the version of the Camel catalog (the default
when null)
+ * @param providerVersion the version of
camel-catalog-provider-springboot (the version of the Camel catalog when
+ * null)
+ * @param springBootGroupId the groupId of
camel-catalog-provider-springboot (org.apache.camel.springboot when null)
+ * @param download whether to download from remote repositories
+ */
+ public static CamelCatalog loadSpringBootCatalog(
+ String repos, String version, String providerVersion, String
springBootGroupId, boolean download)
+ throws Exception {
CamelCatalog answer = new DefaultCamelCatalog();
if (version == null) {
version = answer.getCatalogVersion();
@@ -144,9 +163,8 @@ public final class CatalogLoader {
throw new IOException("Cannot download
org.apache.camel:camel-catalog:" + camelCatalogVersion);
}
- final String camelVersion = version;
- ma = downloader.downloadArtifact(springBootGroupId,
"camel-catalog-provider-springboot",
-
PropertyResolver.fromSystemProperty(CamelJBangConstants.CAMEL_SPRING_BOOT_VERSION,
() -> camelVersion));
+ final String sbVersion = providerVersion != null ? providerVersion
: version;
+ ma = downloader.downloadArtifact(springBootGroupId,
"camel-catalog-provider-springboot", sbVersion);
if (ma != null) {
cl.addFile(ma.getFile());
} else {
@@ -158,8 +176,6 @@ public final class CatalogLoader {
try {
clazz = (Class<RuntimeProvider>)
cl.loadClass(SPRING_BOOT_CATALOG_PROVIDER);
} catch (UnsupportedClassVersionError e) {
- String sbVersion = PropertyResolver.fromSystemProperty(
- CamelJBangConstants.CAMEL_SPRING_BOOT_VERSION, () ->
camelVersion);
throw new IOException(
"Cannot load camel-catalog-provider-springboot:" +
sbVersion
+ " on Java " + Runtime.version()
diff --git
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/MavenResolver.java
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/MavenResolver.java
index 14365abe4380..923c9bbdb471 100644
---
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/MavenResolver.java
+++
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/MavenResolver.java
@@ -23,14 +23,15 @@ import org.apache.camel.tooling.maven.MavenDownloader;
import org.apache.camel.tooling.maven.MavenDownloaderImpl;
/**
- * How Maven artifacts are downloaded: whether downloading is allowed, the
additional repositories, and whether to use
- * fresh (non-cached) resources.
+ * How Maven artifacts are downloaded: whether downloading is allowed, the
additional repositories, the extra default
+ * repositories, and whether to use fresh (non-cached) resources.
*/
public class MavenResolver implements MavenResolverMixinSpec {
private final boolean download;
private final String repos;
private final boolean fresh;
+ private final String extraRepos;
private volatile MavenDownloader downloader;
private final Object downloaderLock = new Object();
@@ -40,9 +41,23 @@ public class MavenResolver implements MavenResolverMixinSpec
{
}
public MavenResolver(boolean download, String repos, boolean fresh) {
+ this(download, repos, fresh, null);
+ }
+
+ /**
+ * @param download whether downloading is allowed
+ * @param repos the additional repositories
+ * @param fresh whether to use fresh (non-cached) resources
+ * @param extraRepos the extra default repositories (comma-separated URLs
or id=url pairs, empty for none), used for
+ * the downloads and in the pom.xml of the exported
project; null for the ones of the
+ * camel.extra.repos system property, else the
camel.default.extra.repos.default.value system
+ * property
+ */
+ public MavenResolver(boolean download, String repos, boolean fresh, String
extraRepos) {
this.download = download;
this.repos = repos;
this.fresh = fresh;
+ this.extraRepos = extraRepos;
}
public static MavenResolver of(Properties props, MavenResolverMixinSpec
fallback) {
@@ -50,7 +65,7 @@ public class MavenResolver implements MavenResolverMixinSpec {
=
Boolean.parseBoolean(props.getProperty(CamelJBangConstants.DOWNLOAD,
String.valueOf(fallback.download())));
String repos = props.getProperty(CamelJBangConstants.REPOS,
fallback.repos());
boolean fresh =
Boolean.parseBoolean(props.getProperty(CamelJBangConstants.FRESH,
String.valueOf(fallback.fresh())));
- return new MavenResolver(download, repos, fresh);
+ return new MavenResolver(download, repos, fresh,
fallback.extraRepos());
}
/** You would typically want to use the properly configured {@link
#downloader()} instead of this value */
@@ -70,6 +85,11 @@ public class MavenResolver implements MavenResolverMixinSpec
{
return fresh;
}
+ @Override
+ public String extraRepos() {
+ return extraRepos;
+ }
+
/**
* @return a lazily and thread-safe initialized {@link MavenDownloader}
configured with {@link #repos}
*/
@@ -82,6 +102,7 @@ public class MavenResolver implements MavenResolverMixinSpec
{
if (repos != null) {
d.setRepos(repos);
}
+ d.setExtraRepos(extraRepos);
d.setOffline(!download);
d.setFresh(fresh);
d.build();
diff --git
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/MavenResolverMixinSpec.java
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/MavenResolverMixinSpec.java
index 69a63cdbca21..9a730762aef2 100644
---
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/MavenResolverMixinSpec.java
+++
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/MavenResolverMixinSpec.java
@@ -22,4 +22,12 @@ public interface MavenResolverMixinSpec {
boolean download();
boolean fresh();
+
+ /**
+ * The extra default repositories (comma-separated URLs or id=url pairs,
empty for none), or null for the ones of
+ * the camel.extra.repos system property.
+ */
+ default String extraRepos() {
+ return null;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/QuarkusPlatform.java
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/QuarkusPlatform.java
index 29b3ed652527..fe54fdc1e1f6 100644
---
a/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/QuarkusPlatform.java
+++
b/dsl/camel-jbang/camel-jbang-export/src/main/java/org/apache/camel/dsl/jbang/export/common/QuarkusPlatform.java
@@ -120,19 +120,11 @@ public class QuarkusPlatform implements
QuarkusPlatformMixinSpec {
}
/**
- * The base URI of the Quarkus Extension Registry: the configured one,
else the one of the
- * camel.jbang.quarkus.platform.url system property (without its
"/client/platforms" suffix), else
+ * The base URI of the Quarkus Extension Registry: the given one, else
* {@link RuntimeType#QUARKUS_EXTENSION_REGISTRY_BASE_URL}; without a
trailing slash.
*/
- public static String resolveExtensionRegistryBaseUri(String
quarkusExtensionRegistryBaseUri) {
+ public static String extensionRegistryBaseUri(String
quarkusExtensionRegistryBaseUri) {
String result = quarkusExtensionRegistryBaseUri;
- if (result == null) {
- result =
System.getProperty(QuarkusHelper.QUARKUS_PLATFORM_URL_PROPERTY);
- final String suffix = "/client/platforms";
- if (result != null && result.endsWith(suffix)) {
- result = result.substring(0, result.length() -
suffix.length());
- }
- }
if (result == null) {
result = RuntimeType.QUARKUS_EXTENSION_REGISTRY_BASE_URL;
}
@@ -166,6 +158,6 @@ public class QuarkusPlatform implements
QuarkusPlatformMixinSpec {
@Override
public String quarkusExtensionRegistryBaseUri() {
- return
resolveExtensionRegistryBaseUri(quarkusExtensionRegistryBaseUri);
+ return extensionRegistryBaseUri(quarkusExtensionRegistryBaseUri);
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/ProjectExporterTest.java
b/dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/ProjectExporterTest.java
index 52ab0a150872..5771d3cabb4d 100644
---
a/dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/ProjectExporterTest.java
+++
b/dsl/camel-jbang/camel-jbang-export/src/test/java/org/apache/camel/dsl/jbang/export/ProjectExporterTest.java
@@ -19,14 +19,23 @@ package org.apache.camel.dsl.jbang.export;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.stream.Stream;
import org.apache.camel.dsl.jbang.export.common.CamelHome;
import org.apache.camel.dsl.jbang.export.common.ExportHelper;
+import org.apache.camel.dsl.jbang.export.common.MavenResolver;
+import org.apache.camel.dsl.jbang.export.common.QuarkusPlatform;
import org.apache.camel.dsl.jbang.export.common.RuntimeType;
import org.apache.camel.dsl.jbang.export.common.StringPrinter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
+import org.junitpioneer.jupiter.SetSystemProperty;
import static org.assertj.core.api.Assertions.assertThat;
@@ -39,11 +48,11 @@ class ProjectExporterTest {
@TempDir
Path dir;
- private final List<ExportRequest> resolved = new ArrayList<>();
+ private final List<ExportRequest> resolved =
Collections.synchronizedList(new ArrayList<>());
private final DependencyResolver resolver = request -> {
resolved.add(request);
- Path settings =
CamelHome.getWorkDir().resolve(ExportHelper.RUN_SETTINGS_FILE);
+ Path settings =
request.workDir.resolve(ExportHelper.RUN_SETTINGS_FILE);
Files.createDirectories(settings.getParent());
Files.writeString(settings, """
camel.main.routesIncludePattern=file:%s
@@ -55,21 +64,9 @@ class ProjectExporterTest {
@Test
void exportsCamelMainProject() throws Exception {
- Path route = dir.resolve("hello.camel.yaml");
- Files.writeString(route, """
- - from:
- uri: timer:hello
- steps:
- - to: log:hello
- """);
+ Path route = writeRoute("hello");
Path exportDir = dir.resolve("export");
-
- ExportRequest request = new ExportRequest();
- request.files.add(route.toString());
- request.runtime = RuntimeType.main;
- request.exportDir = exportDir.toString();
- request.mavenWrapper = false;
- request.quiet = true;
+ ExportRequest request = request(route, exportDir);
StringPrinter printer = new StringPrinter();
ExportContext context = new
ExportContext().withPrinter(printer).withDependencyResolver(resolver);
@@ -85,6 +82,42 @@ class ProjectExporterTest {
assertThat(pom).contains("<artifactId>hello</artifactId>",
"<artifactId>camel-timer</artifactId>",
"<artifactId>camel-log</artifactId>");
assertThat(exportDir.resolve("src/main/resources/camel/hello.camel.yaml")).hasSameTextualContentAs(route);
+ // the run settings and the project are built in the work directory of
the request
+
assertThat(request.workDir.resolve(ExportHelper.RUN_SETTINGS_FILE)).exists();
+ }
+
+ @Test
+ void exportsInParallelWithWorkDirsOfTheirOwn() throws Exception {
+ List<String> before = listWorkDir();
+
+ int count = 4;
+ List<Callable<Integer>> tasks = new ArrayList<>();
+ for (int i = 0; i < count; i++) {
+ Path route = writeRoute("hello" + i);
+ ExportRequest request = request(route, dir.resolve("export" + i));
+ tasks.add(() -> new ProjectExporter(
+ new ExportContext().withPrinter(new
StringPrinter()).withDependencyResolver(resolver))
+ .export(request));
+ }
+ ExecutorService executor = Executors.newFixedThreadPool(count);
+ try {
+ for (Future<Integer> exit : executor.invokeAll(tasks)) {
+ assertThat(exit.get()).isZero();
+ }
+ } finally {
+ executor.shutdownNow();
+ }
+
+ for (int i = 0; i < count; i++) {
+ Path exportDir = dir.resolve("export" + i);
+ String route = "hello" + i + ".camel.yaml";
+
assertThat(Files.readString(exportDir.resolve("pom.xml"))).contains("<artifactId>hello"
+ i + "</artifactId>");
+ // each project has its own route only
+
assertThat(exportDir.resolve("src/main/resources/camel")).isDirectoryContaining("glob:**/"
+ route)
+ .isDirectoryNotContaining(p ->
!p.getFileName().toString().equals(route));
+ }
+ // nothing is written to the .camel-jbang directory of the current
directory
+ assertThat(listWorkDir()).isEqualTo(before);
}
@Test
@@ -116,6 +149,99 @@ class ProjectExporterTest {
assertThat(resolved).isEmpty();
}
+ private ExportContext exportContext() {
+ return new ExportContext().withPrinter(new
StringPrinter()).withDependencyResolver(resolver);
+ }
+
+ private Path writeRoute(String name) throws Exception {
+ Path route = dir.resolve(name + ".camel.yaml");
+ Files.writeString(route, """
+ - from:
+ uri: timer:%s
+ steps:
+ - to: log:%s
+ """.formatted(name, name));
+ return route;
+ }
+
+ private ExportRequest request(Path route, Path exportDir) {
+ ExportRequest request = new ExportRequest();
+ request.files.add(route.toString());
+ request.runtime = RuntimeType.main;
+ request.exportDir = exportDir.toString();
+ request.workDir = exportDir.resolveSibling(exportDir.getFileName() +
"-work");
+ request.mavenWrapper = false;
+ request.quiet = true;
+ return request;
+ }
+
+ private static List<String> listWorkDir() throws Exception {
+ Path work = CamelHome.getWorkDir();
+ if (!Files.exists(work)) {
+ return List.of();
+ }
+ try (Stream<Path> files = Files.walk(work)) {
+ return files.map(p -> p + ":" +
p.toFile().lastModified()).sorted().toList();
+ }
+ }
+
+ @Test
+ @SetSystemProperty(key = "camel.extra.repos", value =
"sysprop=https://sysprop.example.com/maven2")
+ void extraReposOfTheRequestInThePom() throws Exception {
+ Path exportDir = dir.resolve("export");
+ ExportRequest request = request(writeRoute("hello"), exportDir);
+ request.mavenResolver = new MavenResolver(true, null, false,
"explicit=https://explicit.example.com/maven2");
+
+ assertThat(new
ProjectExporter(exportContext()).export(request)).isZero();
+
+ // the extra repositories of the request win over the system property
+ assertThat(Files.readString(exportDir.resolve("pom.xml")))
+ .contains("<url>https://explicit.example.com/maven2</url>")
+ .doesNotContain("sysprop.example.com");
+ }
+
+ @Test
+ @SetSystemProperty(key = "camel.extra.repos", value =
"sysprop=https://sysprop.example.com/maven2")
+ void noExtraRepos() throws Exception {
+ Path exportDir = dir.resolve("export");
+ ExportRequest request = request(writeRoute("hello"), exportDir);
+ request.mavenResolver = new MavenResolver(true, null, false, "");
+
+ assertThat(new
ProjectExporter(exportContext()).export(request)).isZero();
+
+
assertThat(Files.readString(exportDir.resolve("pom.xml"))).doesNotContain("sysprop.example.com");
+ }
+
+ @Test
+ @SetSystemProperty(key = "camel.extra.repos", value =
"sysprop=https://sysprop.example.com/maven2")
+ void extraReposOfTheSystemProperty() throws Exception {
+ Path exportDir = dir.resolve("export");
+ ExportRequest request = request(writeRoute("hello"), exportDir);
+
+ assertThat(new
ProjectExporter(exportContext()).export(request)).isZero();
+
+ // as before the request had extra repositories
+
assertThat(Files.readString(exportDir.resolve("pom.xml"))).contains("<url>https://sysprop.example.com/maven2</url>");
+ }
+
+ @Test
+ @SetSystemProperty(key = "camel.jbang.camelSpringBootVersion", value =
"9.9.9")
+ @SetSystemProperty(key = "camel.jbang.quarkusVersion", value = "9.9.9")
+ @SetSystemProperty(key = "camel.jbang.quarkus.platform.url", value =
"https://registry.example.com/client/platforms")
+ void prepareReadsNoSystemProperties() throws Exception {
+ ExportRequest request = new ExportRequest();
+ request.exportBaseDir = dir;
+
+ new ProjectExporter(new ExportContext().withPrinter(new
StringPrinter()).withDependencyResolver(resolver))
+ .prepare(request);
+
+ // the CLI sets what the system properties configure on the request
+ assertThat(request.camelSpringBootVersion).isNull();
+ assertThat(request.quarkusPlatform.quarkusVersion()).isNull();
+ assertThat(request.quarkusPlatform.quarkusExtensionRegistryBaseUri())
+
.isEqualTo(QuarkusPlatform.extensionRegistryBaseUri(RuntimeType.QUARKUS_EXTENSION_REGISTRY_BASE_URL));
+ }
+
@Test
void projectNameAndVersion() {
assertThat(ProjectExporter.projectName("foo", "com.acme:bar:2.0",
List.of("baz.yaml"))).isEqualTo("foo");
diff --git
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
index 7e25887ac230..32df6dbcebca 100644
---
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
+++
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
@@ -134,6 +134,7 @@ public class KameletMain extends MainCommandLineSupport {
private boolean download = true;
private boolean packageScanJars;
private String repositories;
+ private String extraRepositories;
private boolean fresh;
private boolean verbose;
private String mavenSettings;
@@ -288,6 +289,19 @@ public class KameletMain extends MainCommandLineSupport {
this.repositories = repositories;
}
+ public String getExtraRepositories() {
+ return extraRepositories;
+ }
+
+ /**
+ * The extra default maven repositories for download on-demand
(comma-separated URLs or id=url pairs, empty for
+ * none). When not set, they are the ones of the camel.extra.repos system
property, else the
+ * camel.default.extra.repos.default.value system property.
+ */
+ public void setExtraRepositories(String extraRepositories) {
+ this.extraRepositories = extraRepositories;
+ }
+
public boolean isFresh() {
return fresh;
}
@@ -512,7 +526,7 @@ public class KameletMain extends MainCommandLineSupport {
CircuitBreakerDownloader.registerDownloadReifiers();
// in case we use transacted
- TransactedDownloader.registerDownloadReifiers(this);
+ TransactedDownloader.registerDownloadReifiers();
// in case we use saga
SagaDownloader.registerDownloadReifiers();
@@ -825,6 +839,7 @@ public class KameletMain extends MainCommandLineSupport {
downloader.setCamelContext(answer);
downloader.setVerbose(verbose);
downloader.setRepositories(repositories);
+ downloader.setExtraRepositories(extraRepositories);
downloader.setFresh(fresh);
downloader.setMavenSettings(mavenSettings);
downloader.setMavenSettingsSecurity(mavenSettingsSecurity);
diff --git
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/MavenDependencyDownloader.java
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/MavenDependencyDownloader.java
index 5ec4b2b4b7a1..977f37127ed8 100644
---
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/MavenDependencyDownloader.java
+++
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/MavenDependencyDownloader.java
@@ -83,6 +83,8 @@ public class MavenDependencyDownloader extends ServiceSupport
implements Depende
// repository URLs set from "camel.jbang.repos" property or --repos option.
private String repositories;
+ // the extra default repositories, or null for the ones of the
camel.extra.repos system property
+ private String extraRepositories;
private boolean fresh;
// settings.xml and settings-security.xml locations to be passed to
MavenDownloader from camel-tooling-maven
@@ -162,6 +164,19 @@ public class MavenDependencyDownloader extends
ServiceSupport implements Depende
return repositories;
}
+ public String getExtraRepositories() {
+ return extraRepositories;
+ }
+
+ /**
+ * The extra default repositories (comma-separated URLs or id=url pairs,
empty for none), used in addition to Maven
+ * Central, the Maven settings and the repositories. When not set, they
are the ones of the camel.extra.repos system
+ * property, else the camel.default.extra.repos.default.value system
property.
+ */
+ public void setExtraRepositories(String extraRepositories) {
+ this.extraRepositories = extraRepositories;
+ }
+
@Override
public void setRepositories(String repositories) {
this.repositories = repositories;
@@ -593,6 +608,7 @@ public class MavenDependencyDownloader extends
ServiceSupport implements Depende
mavenDownloaderImpl.setMavenCentralEnabled(mavenCentralEnabled);
mavenDownloaderImpl.setMavenApacheSnapshotEnabled(mavenApacheSnapshotEnabled);
mavenDownloaderImpl.setRepos(repositories);
+ mavenDownloaderImpl.setExtraRepos(extraRepositories);
mavenDownloaderImpl.setFresh(fresh);
mavenDownloaderImpl.setOffline(!download);
mavenDownloaderImpl.setPreferLocal(preferLocal);
diff --git
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/TransactedDownloader.java
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/TransactedDownloader.java
index 339abd473a61..9f8f14474dfc 100644
---
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/TransactedDownloader.java
+++
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/TransactedDownloader.java
@@ -21,10 +21,10 @@ import java.util.Arrays;
import org.apache.camel.NamedNode;
import org.apache.camel.Processor;
import org.apache.camel.Route;
-import org.apache.camel.main.KameletMain;
import org.apache.camel.model.TransactedDefinition;
import org.apache.camel.reifier.ProcessReifier;
import org.apache.camel.reifier.ProcessorReifier;
+import org.apache.camel.spi.Registry;
import org.apache.camel.spi.TransactedPolicy;
/**
@@ -45,7 +45,9 @@ public class TransactedDownloader {
private TransactedDownloader() {
}
- public static void registerDownloadReifiers(KameletMain main) {
+ public static void registerDownloadReifiers() {
+ // the reifiers are static (for the JVM), so they refer to the
CamelContext of the route only, not to the
+ // KameletMain that registers them, which would otherwise stay
referenced when it is done
ProcessorReifier.registerReifier(TransactedDefinition.class,
(route, processorDefinition) -> {
if (processorDefinition instanceof TransactedDefinition) {
@@ -54,7 +56,8 @@ public class TransactedDownloader {
downloader.downloadDependency("org.apache.camel",
"camel-jta",
route.getCamelContext().getVersion());
TransactedPolicy policy = new
DummyTransactedPolicy();
- Arrays.stream(TRANSACTED_POLICIES).forEach(p ->
main.bind(p, policy));
+ Registry registry =
route.getCamelContext().getRegistry();
+ Arrays.stream(TRANSACTED_POLICIES).forEach(p ->
registry.bind(p, policy));
}
}
return ProcessReifier.coreReifier(route,
processorDefinition);
diff --git
a/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloader.java
b/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloader.java
index eade50acf9a6..480e171621c9 100644
---
a/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloader.java
+++
b/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloader.java
@@ -155,6 +155,35 @@ public interface MavenDownloader extends Service {
*/
void setRepos(String repos);
+ /**
+ * Configures the extra default repositories, which are used in addition
to Maven Central, the repositories of the
+ * Maven settings and the configured repositories: a comma-separated list
of URLs or {@code id=url} pairs. An empty
+ * value is no extra repositories, and null (the default) takes them from
the {@code camel.extra.repos} system
+ * property, else the {@code camel.default.extra.repos.default.value}
system property.
+ * <p>
+ * The default implementation throws, rather than ignoring the value: an
implementation that does not support it
+ * would otherwise download from repositories the caller asked not to use
(for example, none at all).
+ *
+ * @since 4.24
+ */
+ default void setExtraRepos(String extraRepos) {
+ throw new UnsupportedOperationException("setExtraRepos is not
supported by " + getClass().getName());
+ }
+
+ /**
+ * The extra default repositories in use (see {@link
#setExtraRepos(String)}): the configured ones, else the ones of
+ * the system properties.
+ * <p>
+ * The default implementation returns null (none), which is what an
implementation that does not support extra
+ * repositories uses.
+ *
+ * @return the comma-separated repositories, or null or empty when there
are none
+ * @since 4.24
+ */
+ default String getExtraRepos() {
+ return null;
+ }
+
/**
* Configure a location of {@code settings-security.xml} (when not set,
defaults to
* {@code ~/.m2/settings-security.xml} unless {@link
#setMavenSettingsLocation(String)} is set explicitly set to
diff --git
a/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloaderImpl.java
b/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloaderImpl.java
index bb5107c96455..65c7e3a87965 100644
---
a/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloaderImpl.java
+++
b/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloaderImpl.java
@@ -114,6 +114,7 @@ public class MavenDownloaderImpl extends ServiceSupport
implements MavenDownload
private String mavenSettings;
private String mavenSettingsSecurity;
private String repos;
+ private String extraRepos;
private boolean fresh;
private boolean offline;
private boolean preferLocal = true;
@@ -516,8 +517,9 @@ public class MavenDownloaderImpl extends ServiceSupport
implements MavenDownload
}
/**
- * Loads extra default Maven repositories from a system property, so they
are used in addition to Maven Central and
- * the repositories from {@code settings.xml} without having to pass
{@code --repos} on every command.
+ * Loads the extra default Maven repositories, so they are used in
addition to Maven Central and the repositories
+ * from {@code settings.xml} without having to pass {@code --repos} on
every command. They are the ones configured
+ * with {@link #setExtraRepos(String)}, else the ones of a system property.
* <p>
* The value is a comma-separated list of repositories, where each entry
is either a plain URL or an {@code id=url}
* pair, for example {@code
repo1=https://repo1.example.com/maven2,repo2=https://repo2.example.com/releases}.
See
@@ -533,16 +535,14 @@ public class MavenDownloaderImpl extends ServiceSupport
implements MavenDownload
* unless configured.
*/
private void loadExtraDefaultRepositories(List<RemoteRepository>
repositories) {
- // user-provided value first, then any baseline a custom distribution
provided
- String sysProp
- = System.getProperty(EXTRA_DEFAULT_REPOS_PROPERTY,
System.getProperty(EXTRA_DEFAULT_REPOS_DEFAULT_VALUE));
- if (sysProp != null && !sysProp.isBlank()) {
- Set<String> repoSpecs = Arrays.stream(sysProp.split("\\s*,\\s*"))
+ String extra = getExtraRepos();
+ if (extra != null && !extra.isBlank()) {
+ Set<String> repoSpecs = Arrays.stream(extra.split("\\s*,\\s*"))
.filter(s -> !s.isBlank())
.collect(Collectors.toCollection(LinkedHashSet::new));
if (!repoSpecs.isEmpty()) {
- LOG.debug("Loaded extra default repositories from system
property: {}", sysProp);
+ LOG.debug("Loaded extra default repositories: {}", extra);
configureRepositories(repositories, repoSpecs);
LOG.debug("Configured {} extra default Maven repositories",
repoSpecs.size());
}
@@ -927,6 +927,20 @@ public class MavenDownloaderImpl extends ServiceSupport
implements MavenDownload
this.repos = repos;
}
+ @Override
+ public void setExtraRepos(String extraRepos) {
+ this.extraRepos = extraRepos;
+ }
+
+ @Override
+ public String getExtraRepos() {
+ if (extraRepos != null) {
+ return extraRepos;
+ }
+ // user-provided value first, then any baseline a custom distribution
provided
+ return System.getProperty(EXTRA_DEFAULT_REPOS_PROPERTY,
System.getProperty(EXTRA_DEFAULT_REPOS_DEFAULT_VALUE));
+ }
+
public void setFresh(boolean fresh) {
this.fresh = fresh;
}
diff --git
a/tooling/camel-tooling-maven/src/test/java/org/apache/camel/tooling/maven/MavenDownloaderImplTest.java
b/tooling/camel-tooling-maven/src/test/java/org/apache/camel/tooling/maven/MavenDownloaderImplTest.java
index 1cdbe1467de3..bca14460d7e1 100644
---
a/tooling/camel-tooling-maven/src/test/java/org/apache/camel/tooling/maven/MavenDownloaderImplTest.java
+++
b/tooling/camel-tooling-maven/src/test/java/org/apache/camel/tooling/maven/MavenDownloaderImplTest.java
@@ -43,6 +43,7 @@ import org.slf4j.LoggerFactory;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -702,6 +703,40 @@ class MavenDownloaderImplTest {
}
}
+ @Test
+ void testExtraReposPrecedence() throws Exception {
+ String original = System.getProperty("camel.extra.repos");
+ String originalDefault =
System.getProperty("camel.default.extra.repos.default.value");
+ try (MavenDownloaderImpl downloader = new MavenDownloaderImpl()) {
+ System.clearProperty("camel.extra.repos");
+ System.clearProperty("camel.default.extra.repos.default.value");
+ assertNull(downloader.getExtraRepos());
+
+ // the fallback for a custom distribution, then the user's system
property: the first one set wins
+ System.setProperty("camel.default.extra.repos.default.value",
"baseline=https://baseline.example.com/maven2");
+ assertEquals("baseline=https://baseline.example.com/maven2",
downloader.getExtraRepos());
+ System.setProperty("camel.extra.repos",
"user=https://user.example.com/maven2");
+ assertEquals("user=https://user.example.com/maven2",
downloader.getExtraRepos());
+
+ // the configured value wins over the system properties, and empty
is no extra repositories
+
downloader.setExtraRepos("explicit=https://explicit.example.com/maven2");
+ assertEquals("explicit=https://explicit.example.com/maven2",
downloader.getExtraRepos());
+ downloader.setExtraRepos("");
+ assertEquals("", downloader.getExtraRepos());
+ } finally {
+ restoreProperty("camel.extra.repos", original);
+ restoreProperty("camel.default.extra.repos.default.value",
originalDefault);
+ }
+ }
+
+ private static void restoreProperty(String key, String value) {
+ if (value != null) {
+ System.setProperty(key, value);
+ } else {
+ System.clearProperty(key);
+ }
+ }
+
@Test
void testResolveArtifactDescriptor() throws Exception {
File repo = createDescriptorFixtureRepository();