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

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


The following commit(s) were added to refs/heads/camel-4.22.x by this push:
     new 33bdd76dfbf5 [backport camel-4.22.x] CAMEL-25239: camel-kamelet-main - 
an import of any class of a Camel component downloads the component (#27293)
33bdd76dfbf5 is described below

commit 33bdd76dfbf529f22893b611767e191260bf41fc
Author: Salvatore Mongiardo <[email protected]>
AuthorDate: Mon Oct 5 12:28:56 2026 +0200

    [backport camel-4.22.x] CAMEL-25239: camel-kamelet-main - an import of any 
class of a Camel component downloads the component (#27293)
    
    Cherry-pick of a58aee231a38 (#27283) onto camel-4.22.x. An import in a Java
    or Groovy source now resolves any class of a Camel component by its package,
    not only the component class itself, so importing e.g. AWS2S3Constants
    downloads camel-aws2-s3 before the route is compiled. Runtime class lookups
    still need the component class, so probing does not download a component.
    
    4.22.x-specific adjustments, as the third-party mapping work of CAMEL-24809
    is not on this branch:
    - KnownDependenciesResolver does not load 
camel-thirdparty-known-dependencies.properties
    - the jackson-annotations mapping is added to 
camel-main-known-dependencies.properties
    - only the component-import test and the 7-line doc paragraph are backported
    - the test accepts ${...} version placeholders, which 
KnownDependenciesVersionResolver
      resolves when the dependency is downloaded
    
    Co-Authored-By: Claus Ibsen <[email protected]>
    Co-Authored-By: Claude Sonnet 4.6 (1M context) <[email protected]>
    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   | 61 +++++++++++++++++++---
 .../camel-main-known-dependencies.properties       |  1 +
 .../download/KnownDependenciesResolverTest.java    | 44 ++++++++++++++++
 5 files changed, 108 insertions(+), 7 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 4d56d7565df9..d561f88dd6fb 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-running.adoc
@@ -224,6 +224,13 @@ To disable automatic downloading:
 camel run foo.java --download=false
 ----
 
+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.
+
 == Running with Spring Boot or Quarkus
 
 Camel CLI can run integrations using Spring Boot or Quarkus runtimes:
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 d2a949333f29..e0af6b1849c1 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
@@ -51,7 +51,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 cae1d090987a..248579988aa7 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,15 +44,15 @@ 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);
     }
 
     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()) {
@@ -61,6 +65,9 @@ public final class KnownDependenciesResolver {
                         String value = prop.getProperty(key);
                         map.put(key, value);
                     }
+                    if (byPackage) {
+                        addPackageMappings(map, componentPackages);
+                    }
                     addMappings(map);
                 }
             }
@@ -69,13 +76,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());
         }
@@ -90,7 +139,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/main/resources/camel-main-known-dependencies.properties
 
b/dsl/camel-kamelet-main/src/main/resources/camel-main-known-dependencies.properties
index 4f98022c4f35..9b1946ae1f53 100644
--- 
a/dsl/camel-kamelet-main/src/main/resources/camel-main-known-dependencies.properties
+++ 
b/dsl/camel-kamelet-main/src/main/resources/camel-main-known-dependencies.properties
@@ -73,3 +73,4 @@ org.apache.qpid.jms.JmsConnectionFactory = 
org.apache.qpid:qpid-jms-client:${qpi
 org.messaginghub.pooled.jms.JmsPoolConnectionFactory = 
org.messaginghub:pooled-jms:${pooled-jms-version}
 org.postgresql.Driver = org.postgresql:postgresql:${pgjdbc-driver-version}
 org.postgresql.ds.PGSimpleDataSource = 
org.postgresql:postgresql:${pgjdbc-driver-version}
+com.fasterxml.jackson.annotation = 
com.fasterxml.jackson.core:jackson-annotations:${jackson2-annotations-version}
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 3eba6ffc2f58..8b3594ca7e21 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
@@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 
 public class KnownDependenciesResolverTest {
 
@@ -53,4 +54,47 @@ public class KnownDependenciesResolverTest {
 
     public static class SomeClass {
     }
+
+    @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.annotation.JsonProperty", 
"com.fasterxml.jackson.core",
+                "jackson-annotations");
+        // 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);
+        assertEquals(groupId, gav.getGroupId(), className);
+        assertEquals(artifactId, gav.getArtifactId(), className);
+        // on this branch a library version can be a ${...} placeholder, 
resolved when the dependency is downloaded
+        assertNotNull(gav.getVersion(), className + " version is null");
+    }
 }

Reply via email to