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 1ad29093abba CAMEL-25720: camel-jbang - load the kamelet catalog and
the Quarkus BOM versions once per JVM (#28005)
1ad29093abba is described below
commit 1ad29093abba79ee9eca44b05c828a06ff471318
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Oct 17 16:33:00 2026 +0200
CAMEL-25720: camel-jbang - load the kamelet catalog and the Quarkus BOM
versions once per JVM (#28005)
Every export built a new KameletsCatalog, which scans the whole classpath,
and
QuarkusHelper read every Quarkus platform BOM with XPath to find its Camel
version (the mapping it keeps on disk is not used for a snapshot, or a
version
that does not match exactly). A released catalog or BOM does not change, so
KameletCatalogHelper.loadKamelets and the managed versions of QuarkusHelper
are
now kept for the JVM; a snapshot is read every time. camel shell, the TUI,
the
MCP server and the tests run several commands in one JVM. ExportTest runs
in 44
seconds instead of 95.
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../commands/catalog/KameletCatalogHelper.java | 22 ++++++++++-
.../camel/dsl/jbang/core/common/QuarkusHelper.java | 25 ++++++++++++
.../commands/catalog/KameletCatalogHelperTest.java | 44 ++++++++++++++++++++++
.../dsl/jbang/core/common/QuarkusHelperTest.java | 23 +++++++++++
4 files changed, 113 insertions(+), 1 deletion(-)
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/KameletCatalogHelper.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/KameletCatalogHelper.java
index 9096ed45a2f5..40d55c6d8c7d 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/KameletCatalogHelper.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/KameletCatalogHelper.java
@@ -19,9 +19,11 @@ package org.apache.camel.dsl.jbang.core.commands.catalog;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import org.apache.camel.main.download.DependencyDownloaderClassLoader;
import org.apache.camel.main.download.MavenDependencyDownloader;
@@ -30,6 +32,9 @@ import org.apache.camel.util.StringHelper;
public final class KameletCatalogHelper {
+ // the kamelets of a released catalog, by version and repositories
+ private static final Map<String, Map<String, Object>> KAMELETS = new
ConcurrentHashMap<>();
+
private KameletCatalogHelper() {
}
@@ -156,6 +161,16 @@ public final class KameletCatalogHelper {
}
public static Map<String, Object> loadKamelets(String version, String
repositories) throws Exception {
+ // a released catalog does not change, and loading it scans the
classpath: a JVM that runs several commands
+ // (camel shell, the TUI, the MCP server, the tests) loads it once
+ boolean release = version != null && !version.endsWith("-SNAPSHOT");
+ String key = version + "|" + repositories;
+ if (release) {
+ Map<String, Object> answer = KAMELETS.get(key);
+ if (answer != null) {
+ return answer;
+ }
+ }
var tccLoader = Thread.currentThread().getContextClassLoader();
try (MavenDependencyDownloader downloader = new
MavenDependencyDownloader()) {
ClassLoader cl = createClassLoader();
@@ -168,7 +183,12 @@ public final class KameletCatalogHelper {
Class<?> clazz =
cl.loadClass("org.apache.camel.kamelets.catalog.KameletsCatalog");
Object catalog = clazz.getDeclaredConstructor().newInstance();
Method m = clazz.getMethod("getKamelets");
- return (Map<String, Object>) ObjectHelper.invokeMethod(m, catalog);
+ Map<String, Object> answer = (Map<String, Object>)
ObjectHelper.invokeMethod(m, catalog);
+ if (release && answer != null) {
+ answer = Collections.unmodifiableMap(answer);
+ KAMELETS.put(key, answer);
+ }
+ return answer;
} finally {
Thread.currentThread().setContextClassLoader(tccLoader);
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/QuarkusHelper.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/QuarkusHelper.java
index f13db66d9450..cd9cb96b9853 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/QuarkusHelper.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/QuarkusHelper.java
@@ -39,9 +39,11 @@ import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Comparator;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -514,6 +516,9 @@ public final class QuarkusHelper {
}
}
+ // the managed versions of a released Quarkus platform BOM, by BOM and
groupId:artifactId
+ private static final Map<String, String> MANAGED_VERSIONS = new
ConcurrentHashMap<>();
+
private record PlatformRelease(ComparableVersion platformVersion, MavenGav
quarkusCamelBomGav) {
PlatformRelease(ComparableVersion platformVersion, MavenGav
quarkusCamelBomGav) {
this.platformVersion = Objects.requireNonNull(platformVersion,
"platformVersion");
@@ -541,6 +546,26 @@ public final class QuarkusHelper {
}
public List<String> findManagedVersions(Function<MavenGav,
MavenArtifact> mavenResolver, Ga... gas) {
+ // a released BOM does not change, and reading it is slow: a JVM
that runs several commands (camel shell,
+ // the TUI, the MCP server, the tests) reads it once
+ final String bom = quarkusCamelBomGav.toMavenResolverString();
+ final boolean release = !bom.endsWith("-SNAPSHOT");
+ if (release) {
+ List<String> cached = Stream.of(gas).map(ga ->
MANAGED_VERSIONS.get(bom + "|" + ga)).toList();
+ if (!cached.contains(null)) {
+ return cached;
+ }
+ }
+ List<String> answer = readManagedVersions(mavenResolver, gas);
+ if (release) {
+ for (int i = 0; i < gas.length; i++) {
+ MANAGED_VERSIONS.put(bom + "|" + gas[i], answer.get(i));
+ }
+ }
+ return answer;
+ }
+
+ private List<String> readManagedVersions(Function<MavenGav,
MavenArtifact> mavenResolver, Ga... gas) {
final MavenArtifact artifact =
mavenResolver.apply(quarkusCamelBomGav);
Path file = artifact.getFile().toPath();
if (!Files.isRegularFile(file)) {
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/catalog/KameletCatalogHelperTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/catalog/KameletCatalogHelperTest.java
new file mode 100644
index 000000000000..e8a1b67c35f4
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/catalog/KameletCatalogHelperTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.catalog;
+
+import java.util.Map;
+
+import org.apache.camel.dsl.jbang.core.common.VersionHelper;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
+
+class KameletCatalogHelperTest {
+
+ @Test
+ void releasedCatalogIsLoadedOnce() throws Exception {
+ String version = VersionHelper.extractKameletsVersion();
+ assumeFalse(version.endsWith("-SNAPSHOT"), "a snapshot catalog is
loaded every time");
+
+ // loading the catalog scans the classpath, so a JVM that runs several
commands loads a released one once
+ Map<String, Object> first = KameletCatalogHelper.loadKamelets(version,
null);
+ Map<String, Object> second =
KameletCatalogHelper.loadKamelets(version, null);
+
+ assertThat(first).containsKey("timer-source");
+ assertThat(second).isSameAs(first);
+ // the callers share it
+ assertThatThrownBy(() ->
first.remove("timer-source")).isInstanceOf(UnsupportedOperationException.class);
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/QuarkusHelperTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/QuarkusHelperTest.java
index 8f94487cf020..532db6175377 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/QuarkusHelperTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/QuarkusHelperTest.java
@@ -83,6 +83,29 @@ public class QuarkusHelperTest {
assertPlatformVersion(streams, "3.17.0", "2.8.0.Final");
}
+ @Test
+ void releasedPlatformBomIsReadOnce() throws IOException,
DeserializationException {
+ String src =
Files.readString(Path.of("target/test-classes/QuarkusHelperTest/quarkus-registry-client-platforms.json"));
+ JsonObject json = (JsonObject) Jsoner.deserialize(src);
+ JsonArray platforms = json.getCollection("platforms");
+ JsonObject platform = platforms.getMap(0);
+ JsonArray streams = platform.getCollection("streams");
+
+ // reads the platform BOMs, unless an earlier test did
+ assertPlatformVersion(streams, "4.18.0", "3.33.1.1");
+
+ // the BOMs of released platforms are not resolved and read again
+ Optional<String> resolved = QuarkusHelper.findPlatformBom(
+ streams, new MajorMinor("4.18.0"),
+ gav -> {
+ throw new IllegalStateException("Maven should not be asked
for " + gav);
+ },
+ RuntimeType.QUARKUS_EXTENSION_REGISTRY_BASE_URL)
+ .map(QuarkusPlatformBom::quarkusCamelBom)
+ .map(MavenGav::getVersion);
+ Assertions.assertThat(resolved).contains("3.33.1.1");
+ }
+
private void assertPlatformVersion(JsonArray streams, String camelVersion,
String expectedPlatformVersion) {
Optional<String> resolved = QuarkusHelper.findPlatformBom(
streams, new MajorMinor(camelVersion),