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

davsclaus pushed a commit to branch fix/CAMEL-25239
in repository https://gitbox.apache.org/repos/asf/camel.git

commit c657f0e5ab207486e9214614ba56a4f4543e2bfc
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Oct 2 14:51:32 2026 +0200

    CAMEL-25239: camel-kamelet-main - an import of any class of a Camel 
component downloads the component
    
    A Java or Groovy source that imports a class of a Camel component, other 
than the component class itself,
    failed to compile in camel run and camel export, for example a route that 
imports
    org.apache.camel.component.aws2.s3.AWS2S3Constants for the headers of the 
aws-s3-source Kamelet. The known
    dependencies map each component by its component class only, so the import 
did not resolve, and the source
    failed to compile before the Kamelet that would bring the component was 
loaded. camel export then failed, or
    with --ignore-loading-error wrote a pom without the Kamelet's dependencies.
    
    The known imports now also find a component by its package (packages shared 
by different dependencies and
    base packages such as org.apache.camel are skipped). Only imports use it: a 
class that is only looked up
    while running still needs the component class itself, so probing for a 
class does not download a component.
    
    Reported by smongiar in #27206.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../modules/ROOT/pages/camel-jbang-running.adoc    |  7 +++
 .../main/download/JavaKnownImportsDownloader.java  |  2 +-
 .../main/download/KnownDependenciesResolver.java   | 63 +++++++++++++++++++---
 .../download/KnownDependenciesResolverTest.java    | 34 ++++++++++++
 4 files changed, 98 insertions(+), 8 deletions(-)

diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc
index 9af930c01171..f4aa1f4799b8 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc
@@ -277,6 +277,13 @@ The lookup walks up from the class name:
 4. The dependency and its transitive dependencies are downloaded and added to 
the classpath,
    and the class is loaded again.
 
+For an `import` in a Java or Groovy source, a Camel component is also found by 
its package, so
+importing any class of a component downloads it, not only the component class 
itself. For
+example, a route that imports 
`org.apache.camel.component.aws2.s3.AWS2S3Constants` for the
+headers of the `aws-s3-source` Kamelet gets `camel-aws2-s3` before the route 
is compiled. A class
+that is only looked up while running still needs the component class itself, 
so probing whether
+a class is present does not download a component.
+
 A dependency you declare yourself, with `--dep`, `camel.jbang.dependencies`, 
or in the project's
 POM, is on the classpath before any lookup, so it always takes precedence. Use 
that when a
 mapped library is not the one you want, for example the ActiveMQ 5 client 
where the mapping
diff --git 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/JavaKnownImportsDownloader.java
 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/JavaKnownImportsDownloader.java
index 70bb7c32673e..9f7dffc9fd2b 100644
--- 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/JavaKnownImportsDownloader.java
+++ 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/JavaKnownImportsDownloader.java
@@ -52,7 +52,7 @@ public class JavaKnownImportsDownloader implements 
CompilePreProcessor {
         List<String> imports = determineImports(code);
         for (String imp : imports) {
             // attempt known dependency resolver first
-            MavenGav gav = knownDependenciesResolver.mavenGavForClass(imp);
+            MavenGav gav = knownDependenciesResolver.mavenGavForImport(imp);
             if (gav != null) {
                 downloadLoader(gav.getGroupId(), gav.getArtifactId(), 
gav.getVersion());
             } else {
diff --git 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/KnownDependenciesResolver.java
 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/KnownDependenciesResolver.java
index 79f8999be675..771274d557cf 100644
--- 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/KnownDependenciesResolver.java
+++ 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/KnownDependenciesResolver.java
@@ -20,8 +20,10 @@ import java.io.InputStream;
 import java.net.URL;
 import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.tooling.maven.MavenGav;
@@ -29,6 +31,8 @@ import org.apache.camel.tooling.maven.MavenGav;
 public final class KnownDependenciesResolver {
 
     private final Map<String, String> mappings = new HashMap<>();
+    // the package of each component, only used for the imports of a source 
(see mavenGavForImport)
+    private final Map<String, String> componentPackages = new HashMap<>();
     private final CamelContext camelContext;
     private final String springBootVersion;
     private final String quarkusVersion;
@@ -40,17 +44,17 @@ public final class KnownDependenciesResolver {
     }
 
     public void loadKnownDependencies() {
-        doLoadKnownDependencies("camel-main-known-dependencies.properties");
-        
doLoadKnownDependencies("camel-component-known-dependencies.properties");
+        doLoadKnownDependencies("camel-main-known-dependencies.properties", 
false);
+        
doLoadKnownDependencies("camel-component-known-dependencies.properties", true);
         // third-party libraries mapped by package, generated from 
src/main/known-third-party-libraries.properties
-        
doLoadKnownDependencies("camel-thirdparty-known-dependencies.properties");
+        
doLoadKnownDependencies("camel-thirdparty-known-dependencies.properties", 
false);
     }
 
     public void loadKnownFactoryFinderDependencies() {
-        
doLoadKnownDependencies("camel-factoryfinder-known-dependencies.properties");
+        
doLoadKnownDependencies("camel-factoryfinder-known-dependencies.properties", 
false);
     }
 
-    private void doLoadKnownDependencies(String name) {
+    private void doLoadKnownDependencies(String name, boolean byPackage) {
         try {
             Enumeration<URL> resources = 
getClass().getClassLoader().getResources(name);
             while (resources.hasMoreElements()) {
@@ -63,6 +67,9 @@ public final class KnownDependenciesResolver {
                         String value = prop.getProperty(key);
                         map.put(key, value);
                     }
+                    if (byPackage) {
+                        addPackageMappings(map, componentPackages);
+                    }
                     addMappings(map);
                 }
             }
@@ -71,13 +78,55 @@ public final class KnownDependenciesResolver {
         }
     }
 
+    /**
+     * The package of each class, unless the package is shared by classes of 
different dependencies, or is a base
+     * package such as <tt>org.apache.camel</tt> that would match every Camel 
class.
+     */
+    private static void addPackageMappings(Map<String, String> classes, 
Map<String, String> packages) {
+        Set<String> shared = new HashSet<>();
+        for (Map.Entry<String, String> entry : classes.entrySet()) {
+            String key = entry.getKey();
+            int pos = key.lastIndexOf('.');
+            if (pos == -1) {
+                continue;
+            }
+            String pkg = key.substring(0, pos);
+            if (pkg.chars().filter(ch -> ch == '.').count() < 3) {
+                continue;
+            }
+            String existing = packages.putIfAbsent(pkg, entry.getValue());
+            if (existing != null && !existing.equals(entry.getValue())) {
+                shared.add(pkg);
+            }
+        }
+        shared.forEach(packages::remove);
+    }
+
     public void addMappings(Map<String, String> mappings) {
         this.mappings.putAll(mappings);
     }
 
     public MavenGav mavenGavForClass(String className) {
+        return toMavenGav(findGav(mappings, className));
+    }
+
+    /**
+     * The dependency of a class a source imports: as {@link 
#mavenGavForClass(String)}, and also any class of a Camel
+     * component (such as the constants of its headers), not only the 
component class itself.
+     * <p/>
+     * Only for imports: a class that is looked up at runtime is often only 
probed for, which must not download a
+     * component.
+     */
+    public MavenGav mavenGavForImport(String className) {
+        String gav = findGav(mappings, className);
+        if (gav == null) {
+            gav = findGav(componentPackages, className);
+        }
+        return toMavenGav(gav);
+    }
+
+    private MavenGav toMavenGav(String gav) {
         MavenGav answer = null;
-        String gav = findGav(className);
         if (gav != null) {
             answer = MavenGav.parseGav(gav, camelContext.getVersion());
         }
@@ -92,7 +141,7 @@ public final class KnownDependenciesResolver {
         return answer;
     }
 
-    private String findGav(String prefix) {
+    private static String findGav(Map<String, String> mappings, String prefix) 
{
         String gav = mappings.get(prefix);
         while (gav == null && prefix.lastIndexOf(".") != -1) {
             prefix = prefix.substring(0, prefix.lastIndexOf("."));
diff --git 
a/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/KnownDependenciesResolverTest.java
 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/KnownDependenciesResolverTest.java
index 15f43fa63f58..90a9f752229f 100644
--- 
a/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/KnownDependenciesResolverTest.java
+++ 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/KnownDependenciesResolverTest.java
@@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test;
 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;
 
 public class KnownDependenciesResolverTest {
 
@@ -84,6 +85,39 @@ public class KnownDependenciesResolverTest {
         assertEquals(null, 
resolver.mavenGavForClass("org.apache.commons.Anything"));
     }
 
+    @Test
+    void anImportResolvesAnyClassOfAComponent() {
+        // CAMEL-25239: any class of a component a source imports, not only 
the component class itself, such as the
+        // constants of the headers of a component that a kamelet uses
+        KnownDependenciesResolver resolver = new KnownDependenciesResolver(new 
SimpleCamelContext(), null, null);
+        resolver.loadKnownDependencies();
+
+        assertImport(resolver, 
"org.apache.camel.component.aws2.s3.AWS2S3Component", "camel-aws2-s3");
+        assertImport(resolver, 
"org.apache.camel.component.aws2.s3.AWS2S3Constants", "camel-aws2-s3");
+        assertImport(resolver, 
"org.apache.camel.component.aws2.s3.utils.AWS2S3Utils", "camel-aws2-s3");
+        // a sibling package of another component is not mistaken for it
+        assertImport(resolver, 
"org.apache.camel.component.aws2.s3vectors.AWS2S3VectorsConstants", 
"camel-aws2-s3-vectors");
+        // a sub package of another component's package wins for its own 
classes
+        assertImport(resolver, 
"org.apache.camel.component.file.remote.SftpConstants", "camel-ftp");
+        assertImport(resolver, "org.apache.camel.component.file.GenericFile", 
"camel-file");
+        // the libraries still resolve as before
+        assertGav(resolver, "com.fasterxml.jackson.databind.ObjectMapper", 
"com.fasterxml.jackson.core", "jackson-databind");
+        // a component in a base package does not claim every class in it
+        assertNull(resolver.mavenGavForImport("org.apache.camel.Exchange"));
+        
assertNull(resolver.mavenGavForImport("org.apache.camel.component.Anything"));
+        // a class looked up at runtime (often only probed for) still needs 
the component class itself
+        
assertNull(resolver.mavenGavForClass("org.apache.camel.component.aws2.s3.AWS2S3Constants"));
+        assertGav(resolver, 
"org.apache.camel.component.aws2.s3.AWS2S3Component", "org.apache.camel", 
"camel-aws2-s3");
+    }
+
+    private static void assertImport(KnownDependenciesResolver resolver, 
String className, String artifactId) {
+        MavenGav gav = resolver.mavenGavForImport(className);
+        assertNotNull(gav, className);
+        assertEquals("org.apache.camel", gav.getGroupId(), className);
+        assertEquals(artifactId, gav.getArtifactId(), className);
+        assertNotNull(gav.getVersion(), className + " version is null");
+    }
+
     private static void assertGav(KnownDependenciesResolver resolver, String 
className, String groupId, String artifactId) {
         MavenGav gav = resolver.mavenGavForClass(className);
         assertNotNull(gav, className);

Reply via email to