You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/10/17 14:33:07 UTC

(camel) branch main updated: CAMEL-25720: camel-jbang - load the kamelet catalog and the Quarkus BOM versions once per JVM (#28005)

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 <cl...@gmail.com>
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) <no...@anthropic.com>
    Signed-off-by: Claus Ibsen <cl...@gmail.com>
---
 .../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),