This is an automated email from the ASF dual-hosted git repository.

ldemasi 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 b22abbe0fb8e CAMEL-23186: [camel-jbang-mcp] 
CatalogLoader.loadCatalog() fails with NoClassDefFoundError for versioned 
catalogs
b22abbe0fb8e is described below

commit b22abbe0fb8e4adeba59efae934b114ed7612705
Author: Luigi De Masi <[email protected]>
AuthorDate: Fri Mar 13 13:52:59 2026 +0100

    CAMEL-23186: [camel-jbang-mcp] CatalogLoader.loadCatalog() fails with 
NoClassDefFoundError for versioned catalogs
---
 .../camel/dsl/jbang/core/common/CatalogLoader.java |  59 ++++++--
 .../dsl/jbang/core/commands/mcp/CatalogTools.java  |  41 +++++-
 .../jbang/core/commands/mcp/CatalogToolsTest.java  | 162 ++++++++++++++++++++-
 3 files changed, 232 insertions(+), 30 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CatalogLoader.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CatalogLoader.java
index d7c91fb88563..9c1606de885c 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CatalogLoader.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CatalogLoader.java
@@ -51,12 +51,21 @@ public final class CatalogLoader {
     }
 
     public static CamelCatalog loadCatalog(String repos, String version, 
boolean download) throws Exception {
+        return loadCatalog(repos, version, null, download);
+    }
+
+    public static CamelCatalog loadCatalog(String repos, String version, 
String catalogGroupId, boolean download)
+            throws Exception {
         CamelCatalog answer = new DefaultCamelCatalog();
         if (version == null || version.isEmpty() || 
version.equals(answer.getCatalogVersion())) {
             answer.enableCache();
             return answer;
         }
 
+        if (catalogGroupId == null) {
+            catalogGroupId = "org.apache.camel";
+        }
+
         DependencyDownloaderClassLoader cl = new 
DependencyDownloaderClassLoader(null);
         MavenDependencyDownloader downloader = new MavenDependencyDownloader();
         downloader.setClassLoader(cl);
@@ -65,23 +74,24 @@ public final class CatalogLoader {
         try {
             downloader.start();
 
-            // download camel-catalog for that specific version
-            MavenArtifact ma = downloader.downloadArtifact("org.apache.camel", 
"camel-catalog", version);
+            // download camel-core-catalog (contains 
AbstractCachingCamelCatalog) and camel-catalog
+            MavenArtifact ma = downloader.downloadArtifact("org.apache.camel", 
"camel-core-catalog", version);
             if (ma != null) {
                 cl.addFile(ma.getFile());
             } else {
-                throw new IOException("Cannot download 
org.apache.camel:camel-catalog:" + version);
+                throw new IOException("Cannot download 
org.apache.camel:camel-core-catalog:" + version);
             }
-
-            // re-create answer with the classloader to be able to load 
resources in this catalog
-            Class<RuntimeProvider> clazz = (Class<RuntimeProvider>) 
cl.loadClass(DEFAULT_CAMEL_CATALOG);
-            if (clazz != null) {
-                answer.setVersionManager(new 
DownloadCatalogVersionManager(version, cl));
-                RuntimeProvider provider = ObjectHelper.newInstance(clazz);
-                if (provider != null) {
-                    answer.setRuntimeProvider(provider);
-                }
+            ma = downloader.downloadArtifact(catalogGroupId, "camel-catalog", 
version);
+            if (ma != null) {
+                cl.addFile(ma.getFile());
+            } else {
+                throw new IOException("Cannot download " + catalogGroupId + 
":camel-catalog:" + version);
             }
+
+            // use the downloaded classloader for catalog resource resolution
+            // keep the current DefaultCamelCatalog instance (which has the 
correct getCatalogVersion behavior)
+            // rather than loading a new instance from the downloaded jar
+            answer.setVersionManager(new 
DownloadCatalogVersionManager(version, cl));
             answer.enableCache();
         } finally {
             downloader.stop();
@@ -92,11 +102,21 @@ public final class CatalogLoader {
     }
 
     public static CamelCatalog loadSpringBootCatalog(String repos, String 
version, boolean download) throws Exception {
+        return loadSpringBootCatalog(repos, version, null, download);
+    }
+
+    public static CamelCatalog loadSpringBootCatalog(
+            String repos, String version, String springBootGroupId, boolean 
download)
+            throws Exception {
         CamelCatalog answer = new DefaultCamelCatalog();
         if (version == null) {
             version = answer.getCatalogVersion();
         }
 
+        if (springBootGroupId == null) {
+            springBootGroupId = "org.apache.camel.springboot";
+        }
+
         DependencyDownloaderClassLoader cl = new 
DependencyDownloaderClassLoader(CatalogLoader.class.getClassLoader());
         MavenDependencyDownloader downloader = new MavenDependencyDownloader();
         downloader.setClassLoader(cl);
@@ -121,13 +141,13 @@ public final class CatalogLoader {
             }
 
             final String camelVersion = version;
-            ma = downloader.downloadArtifact("org.apache.camel.springboot", 
"camel-catalog-provider-springboot",
+            ma = downloader.downloadArtifact(springBootGroupId, 
"camel-catalog-provider-springboot",
                     
PropertyResolver.fromSystemProperty(CamelJBangConstants.CAMEL_SPRING_BOOT_VERSION,
 () -> camelVersion));
             if (ma != null) {
                 cl.addFile(ma.getFile());
             } else {
                 throw new IOException(
-                        "Cannot download 
org.apache.camel.springboot:camel-catalog-provider-springboot:" + version);
+                        "Cannot download " + springBootGroupId + 
":camel-catalog-provider-springboot:" + version);
             }
 
             Class<RuntimeProvider> clazz = (Class<RuntimeProvider>) 
cl.loadClass(SPRING_BOOT_CATALOG_PROVIDER);
@@ -154,6 +174,12 @@ public final class CatalogLoader {
 
     public static CamelCatalog loadQuarkusCatalog(String repos, String 
quarkusVersion, String quarkusGroupId, boolean download)
             throws Exception {
+        return loadQuarkusCatalog(repos, quarkusVersion, quarkusGroupId, null, 
download);
+    }
+
+    public static CamelCatalog loadQuarkusCatalog(
+            String repos, String quarkusVersion, String quarkusGroupId, String 
quarkusBomArtifactId, boolean download)
+            throws Exception {
         String camelQuarkusVersion = null;
         CamelCatalog answer = new DefaultCamelCatalog();
 
@@ -179,7 +205,10 @@ public final class CatalogLoader {
             if (quarkusGroupId == null) {
                 quarkusGroupId = QUARKUS_GROUP_ID;
             }
-            MavenArtifact ma = downloader.downloadArtifact(quarkusGroupId, 
"quarkus-camel-bom:pom", quarkusVersion);
+            if (quarkusBomArtifactId == null) {
+                quarkusBomArtifactId = "quarkus-camel-bom";
+            }
+            MavenArtifact ma = downloader.downloadArtifact(quarkusGroupId, 
quarkusBomArtifactId + ":pom", quarkusVersion);
             if (ma != null && ma.getFile() != null) {
                 String name = ma.getFile().getAbsolutePath();
                 File file = new File(name);
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java
index 02baeb9bb801..c165c179c9d8 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogTools.java
@@ -65,12 +65,16 @@ public class CatalogTools {
             @ToolArg(description = "Version to query. For Main or Spring Boot: 
the Camel version (e.g., 4.17.0). "
                                    + "For quarkus: the Quarkus Platform 
version (e.g., 3.31.3) as returned by "
                                    + "camel_version_list quarkusVersion field. 
"
-                                   + "If not specified, uses the default 
catalog version.") String camelVersion) {
+                                   + "If not specified, uses the default 
catalog version.") String camelVersion,
+            @ToolArg(description = "Platform BOM coordinates in GAV format 
(groupId:artifactId:version). "
+                                   + "When provided, overrides camelVersion. 
For quarkus runtime, all three coordinates "
+                                   + "are used for BOM resolution. For main 
and spring-boot, the version is extracted "
+                                   + "and used as the catalog version.") 
String platformBom) {
 
         int maxResults = limit != null ? limit : 50;
 
         try {
-            CamelCatalog cat = loadCatalog(runtime, camelVersion);
+            CamelCatalog cat = loadCatalog(runtime, camelVersion, platformBom);
 
             List<ComponentInfo> components = findComponentNames(cat).stream()
                     .map(cat::componentModel)
@@ -106,14 +110,18 @@ public class CatalogTools {
             @ToolArg(description = "Version to query. For Main or Spring Boot: 
the Camel version (e.g., 4.17.0). "
                                    + "For quarkus: the Quarkus Platform 
version (e.g., 3.31.3) as returned by "
                                    + "camel_version_list quarkusVersion field. 
"
-                                   + "If not specified, uses the default 
catalog version.") String camelVersion) {
+                                   + "If not specified, uses the default 
catalog version.") String camelVersion,
+            @ToolArg(description = "Platform BOM coordinates in GAV format 
(groupId:artifactId:version). "
+                                   + "When provided, overrides camelVersion. 
For quarkus runtime, all three coordinates "
+                                   + "are used for BOM resolution. For main 
and spring-boot, the version is extracted "
+                                   + "and used as the catalog version.") 
String platformBom) {
 
         if (component == null || component.isBlank()) {
             throw new ToolCallException("Component name is required", null);
         }
 
         try {
-            CamelCatalog cat = loadCatalog(runtime, camelVersion);
+            CamelCatalog cat = loadCatalog(runtime, camelVersion, platformBom);
             ComponentModel model = cat.componentModel(component);
             if (model == null) {
                 // Check if it might be a data format or language instead
@@ -311,12 +319,32 @@ public class CatalogTools {
 
     // Catalog loading
 
-    private CamelCatalog loadCatalog(String runtime, String camelVersion) 
throws Exception {
+    private CamelCatalog loadCatalog(String runtime, String camelVersion, 
String platformBom) throws Exception {
         String repos = catalogRepos.orElse(null);
+        RuntimeType runtimeType = resolveRuntime(runtime);
+
+        // If platformBom is provided (GAV format), parse and use it
+        if (platformBom != null && !platformBom.isBlank()) {
+            String[] parts = platformBom.split(":");
+            if (parts.length != 3) {
+                throw new ToolCallException(
+                        "platformBom must be in GAV format 
(groupId:artifactId:version), got: " + platformBom, null);
+            }
+            String groupId = parts[0];
+            String artifactId = parts[1];
+            String version = parts[2];
+
+            if (runtimeType == RuntimeType.quarkus) {
+                return CatalogLoader.loadQuarkusCatalog(repos, version, 
groupId, artifactId, true);
+            } else if (runtimeType == RuntimeType.springBoot) {
+                return CatalogLoader.loadSpringBootCatalog(repos, version, 
groupId, true);
+            } else {
+                return CatalogLoader.loadCatalog(repos, version, groupId, 
true);
+            }
+        }
 
         // If a specific version is requested, load that version's catalog
         if (camelVersion != null && !camelVersion.isBlank()) {
-            RuntimeType runtimeType = resolveRuntime(runtime);
             if (runtimeType == RuntimeType.springBoot) {
                 return CatalogLoader.loadSpringBootCatalog(repos, 
camelVersion, true);
             } else if (runtimeType == RuntimeType.quarkus) {
@@ -327,7 +355,6 @@ public class CatalogTools {
         }
 
         // No specific version, use runtime-specific catalog or default
-        RuntimeType runtimeType = resolveRuntime(runtime);
         if (runtimeType == RuntimeType.springBoot) {
             return CatalogLoader.loadSpringBootCatalog(repos, null, true);
         } else if (runtimeType == RuntimeType.quarkus) {
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogToolsTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogToolsTest.java
index 1a7283cac144..38c5d87b4c4f 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogToolsTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/CatalogToolsTest.java
@@ -18,12 +18,18 @@ package org.apache.camel.dsl.jbang.core.commands.mcp;
 
 import java.util.Optional;
 
+import io.quarkiverse.mcp.server.ToolCallException;
+import org.apache.camel.catalog.DefaultCamelCatalog;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 class CatalogToolsTest {
 
+    private static final String BUILTIN_VERSION = new 
DefaultCamelCatalog().getCatalogVersion();
+
     private CatalogTools createTools(String repos) {
         CatalogTools tools = new CatalogTools();
         tools.catalogRepos = Optional.ofNullable(repos);
@@ -34,8 +40,7 @@ class CatalogToolsTest {
     void defaultCatalogWithNoRepos() {
         CatalogTools tools = createTools(null);
 
-        // Should use the default catalog (no version specified, no repos)
-        CatalogTools.ComponentListResult result = 
tools.camel_catalog_components(null, null, 5, null, null);
+        CatalogTools.ComponentListResult result = 
tools.camel_catalog_components(null, null, 5, null, null, null);
 
         assertThat(result).isNotNull();
         assertThat(result.camelVersion()).isNotNull();
@@ -45,7 +50,7 @@ class CatalogToolsTest {
     void defaultCatalogWithEmptyRepos() {
         CatalogTools tools = createTools("");
 
-        CatalogTools.ComponentListResult result = 
tools.camel_catalog_components(null, null, 5, null, null);
+        CatalogTools.ComponentListResult result = 
tools.camel_catalog_components(null, null, 5, null, null, null);
 
         assertThat(result).isNotNull();
         assertThat(result.camelVersion()).isNotNull();
@@ -55,8 +60,7 @@ class CatalogToolsTest {
     void catalogWithExtraRepos() {
         CatalogTools tools = 
createTools("https://maven.repository.redhat.com/ga/";);
 
-        // Should still work — the extra repo is added but default catalog 
doesn't need it
-        CatalogTools.ComponentListResult result = 
tools.camel_catalog_components(null, null, 5, null, null);
+        CatalogTools.ComponentListResult result = 
tools.camel_catalog_components(null, null, 5, null, null, null);
 
         assertThat(result).isNotNull();
         assertThat(result.camelVersion()).isNotNull();
@@ -67,7 +71,7 @@ class CatalogToolsTest {
         CatalogTools tools = createTools(
                 
"https://maven.repository.redhat.com/ga/,https://repository.jboss.org/nexus/content/groups/public/";);
 
-        CatalogTools.ComponentListResult result = 
tools.camel_catalog_components(null, null, 5, null, null);
+        CatalogTools.ComponentListResult result = 
tools.camel_catalog_components(null, null, 5, null, null, null);
 
         assertThat(result).isNotNull();
         assertThat(result.camelVersion()).isNotNull();
@@ -77,10 +81,152 @@ class CatalogToolsTest {
     void componentDocWithRepos() {
         CatalogTools tools = 
createTools("https://maven.repository.redhat.com/ga/";);
 
-        // timer is a core component, should always be available
-        CatalogTools.ComponentDetailResult result = 
tools.camel_catalog_component_doc("timer", null, null);
+        CatalogTools.ComponentDetailResult result = 
tools.camel_catalog_component_doc("timer", null, null, null);
 
         assertThat(result).isNotNull();
         assertThat(result.name()).isEqualTo("timer");
     }
+
+    // platformBom validation tests
+
+    @Test
+    void platformBomInvalidFormatThrows() {
+        CatalogTools tools = createTools(null);
+
+        assertThatThrownBy(() -> tools.camel_catalog_components(null, null, 5, 
null, null, "invalid-format"))
+                .isInstanceOf(ToolCallException.class)
+                .hasMessageContaining("GAV format");
+    }
+
+    @Test
+    void platformBomInvalidFormatTwoPartsThrows() {
+        CatalogTools tools = createTools(null);
+
+        assertThatThrownBy(() -> tools.camel_catalog_components(null, null, 5, 
null, null, "group:artifact"))
+                .isInstanceOf(ToolCallException.class)
+                .hasMessageContaining("GAV format");
+    }
+
+    @Test
+    void platformBomNullDoesNotThrow() {
+        CatalogTools tools = createTools(null);
+
+        CatalogTools.ComponentListResult result = 
tools.camel_catalog_components(null, null, 5, null, null, null);
+
+        assertThat(result).isNotNull();
+        assertThat(result.camelVersion()).isNotNull();
+    }
+
+    @Test
+    void platformBomEmptyStringDoesNotThrow() {
+        CatalogTools tools = createTools(null);
+
+        CatalogTools.ComponentListResult result = 
tools.camel_catalog_components(null, null, 5, null, null, "");
+
+        assertThat(result).isNotNull();
+        assertThat(result.camelVersion()).isNotNull();
+    }
+
+    @Test
+    void platformBomBlankStringDoesNotThrow() {
+        CatalogTools tools = createTools(null);
+
+        CatalogTools.ComponentListResult result = 
tools.camel_catalog_components(null, null, 5, null, null, "   ");
+
+        assertThat(result).isNotNull();
+        assertThat(result.camelVersion()).isNotNull();
+    }
+
+    // Version reporting tests
+
+    @Test
+    void defaultCatalogReturnsBuiltinVersion() {
+        CatalogTools tools = createTools(null);
+
+        CatalogTools.ComponentListResult result = 
tools.camel_catalog_components(null, null, 5, null, null, null);
+
+        assertThat(result.camelVersion()).isEqualTo(BUILTIN_VERSION);
+    }
+
+    @Test
+    void emptyVersionAndPlatformBomReturnsBuiltinVersion() {
+        CatalogTools tools = createTools(null);
+
+        CatalogTools.ComponentListResult result = 
tools.camel_catalog_components(null, null, 5, null, "", "");
+
+        assertThat(result.camelVersion()).isEqualTo(BUILTIN_VERSION);
+    }
+
+    @Test
+    void specificCamelVersionReturnsRequestedVersion() {
+        CatalogTools tools = createTools(null);
+
+        CatalogTools.ComponentListResult result
+                = tools.camel_catalog_components(null, null, 5, null, 
BUILTIN_VERSION, null);
+
+        assertThat(result.camelVersion()).isEqualTo(BUILTIN_VERSION);
+    }
+
+    @Test
+    void platformBomVersionReturnsRequestedVersion() {
+        CatalogTools tools = createTools(null);
+
+        String bom = "org.apache.camel:camel-catalog:" + BUILTIN_VERSION;
+        CatalogTools.ComponentListResult result = 
tools.camel_catalog_components(null, null, 5, null, null, bom);
+
+        assertThat(result.camelVersion()).isEqualTo(BUILTIN_VERSION);
+    }
+
+    @Test
+    void platformBomVersionTakesPrecedenceOverCamelVersion() {
+        CatalogTools tools = createTools(null);
+
+        String bom = "org.apache.camel:camel-catalog:" + BUILTIN_VERSION;
+        CatalogTools.ComponentListResult result
+                = tools.camel_catalog_components(null, null, 5, null, 
"9.99.99", bom);
+
+        assertThat(result.camelVersion()).isEqualTo(BUILTIN_VERSION);
+    }
+
+    // Download tests — require Maven Central access.
+    // Disabled in CI environments to avoid flaky network-dependent failures.
+
+    @Test
+    @DisabledIfSystemProperty(named = "ci.env.name", matches = ".*",
+                              disabledReason = "Runs only local — requires 
Maven Central access")
+    void downloadedCatalogVersionDiffersFromBuiltin() {
+        CatalogTools tools = createTools(null);
+
+        String requestedVersion = "4.10.0";
+        CatalogTools.ComponentListResult listResult
+                = tools.camel_catalog_components("timer", null, 1, "main", 
requestedVersion, null);
+
+        assertThat(listResult.camelVersion()).isEqualTo(requestedVersion);
+
+        CatalogTools.ComponentDetailResult docResult
+                = tools.camel_catalog_component_doc("timer", "main", 
requestedVersion, null);
+
+        assertThat(docResult.version()).isEqualTo(requestedVersion);
+        assertThat(docResult.version()).isNotEqualTo(BUILTIN_VERSION);
+    }
+
+    @Test
+    @DisabledIfSystemProperty(named = "ci.env.name", matches = ".*",
+                              disabledReason = "Runs only local — requires 
Maven Central access")
+    void downloadedCatalogViaPlatformBomVersionDiffersFromBuiltin() {
+        CatalogTools tools = createTools(null);
+
+        String requestedVersion = "4.10.0";
+        String bom = "org.apache.camel:camel-catalog:" + requestedVersion;
+        CatalogTools.ComponentListResult listResult
+                = tools.camel_catalog_components("timer", null, 1, "main", 
null, bom);
+
+        assertThat(listResult.camelVersion()).isEqualTo(requestedVersion);
+
+        CatalogTools.ComponentDetailResult docResult
+                = tools.camel_catalog_component_doc("timer", "main", null, 
bom);
+
+        assertThat(docResult.version()).isEqualTo(requestedVersion);
+        assertThat(docResult.version()).isNotEqualTo(BUILTIN_VERSION);
+    }
 }

Reply via email to