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 a19029eb9303 CAMEL-26901: camel-jbang - camel run leaves out what is 
already on the classpath when resolving dependencies, also with the launcher 
(#29777)
a19029eb9303 is described below

commit a19029eb9303c9334a9c1f32f77c66a489f74652
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Nov 13 08:03:14 2026 +0100

    CAMEL-26901: camel-jbang - camel run leaves out what is already on the 
classpath when resolving dependencies, also with the launcher (#29777)
    
    camel run leaves the JARs on the classpath the JVM was started with out of 
the
    dependency resolution, with their own dependencies, through a dependency
    selector, so neither they nor their POMs are downloaded
    (MavenDownloader.resolveArtifacts with an available predicate).
    
    - BootClasspath reads the manifest Class-Path of java -jar, so the launcher
      distribution matches what is in its lib
    - the classpath check no longer replaces the dots in an artifact id
    - camel hawtio adds the classpath to its parentless class loader
    
    A small route on the launcher: cold run 13.7s -> 7.0s, 43 -> 10 JARs 
downloaded.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../apache/camel/main/download/BootClasspath.java  | 134 +++++++++++++++++++++
 .../main/download/MavenDependencyDownloader.java   |  75 ++++++++----
 .../camel/main/download/BootClasspathTest.java     |  86 +++++++++++++
 .../camel/tooling/maven/MavenDownloader.java       |  17 +++
 .../camel/tooling/maven/MavenDownloaderImpl.java   |  56 +++++++++
 .../tooling/maven/MavenDownloaderImplTest.java     |  51 ++++++++
 6 files changed, 394 insertions(+), 25 deletions(-)

diff --git 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/BootClasspath.java
 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/BootClasspath.java
new file mode 100644
index 000000000000..03acdc74c4a9
--- /dev/null
+++ 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/BootClasspath.java
@@ -0,0 +1,134 @@
+/*
+ * 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.main.download;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import org.apache.camel.util.FileUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The JARs on the classpath the JVM was started with, such as the Camel JARs 
of the Camel CLI.
+ * <p>
+ * A JVM started with {@code java -jar}, as the Camel launcher is, has only 
that JAR as its classpath: the other JARs
+ * are listed in the {@code Class-Path} of its manifest, so they are read from 
there.
+ */
+final class BootClasspath {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(BootClasspath.class);
+
+    private final List<String> entries;
+    private final Set<String> jarNames = new HashSet<>();
+
+    private BootClasspath(List<String> entries) {
+        this.entries = entries;
+        for (String entry : entries) {
+            String name = FileUtil.stripPath(entry);
+            if (name.endsWith(".jar")) {
+                jarNames.add(name);
+            }
+        }
+    }
+
+    /**
+     * The JARs of the classpath the JVM was started with.
+     */
+    static BootClasspath of(String classpath) {
+        List<String> entries = new ArrayList<>();
+        if (classpath != null && !classpath.isBlank()) {
+            for (String entry : classpath.split(File.pathSeparator)) {
+                if (!entry.isBlank()) {
+                    entries.add(entry);
+                }
+            }
+        }
+        if (entries.size() == 1 && entries.get(0).endsWith(".jar")) {
+            // java -jar: the JARs are in the Class-Path of its manifest
+            entries.addAll(manifestClassPath(new File(entries.get(0))));
+        }
+        return new BootClasspath(entries);
+    }
+
+    private static List<String> manifestClassPath(File jar) {
+        List<String> answer = new ArrayList<>();
+        if (!jar.isFile()) {
+            return answer;
+        }
+        try (JarFile jf = new JarFile(jar)) {
+            Manifest manifest = jf.getManifest();
+            String classPath = manifest != null ? 
manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH) : null;
+            if (classPath != null) {
+                // relative URLs, resolved against the directory of the JAR
+                URI base = jar.getAbsoluteFile().getParentFile().toURI();
+                for (String url : classPath.trim().split("\\s+")) {
+                    if (!url.isEmpty()) {
+                        try {
+                            answer.add(new File(base.resolve(url)).getPath());
+                        } catch (IllegalArgumentException e) {
+                            // not a file URL
+                            answer.add(url);
+                        }
+                    }
+                }
+            }
+        } catch (IOException e) {
+            LOG.debug("Cannot read the Class-Path of the manifest of: {} due 
to: {}", jar, e.getMessage(), e);
+        }
+        return answer;
+    }
+
+    /**
+     * Whether an entry of the classpath contains the text, such as 
artifactId-version.
+     */
+    boolean contains(String text) {
+        for (String entry : entries) {
+            if (entry.contains(text)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Whether the JAR of the artifact is on the classpath: the file name is 
artifactId-version.jar, or
+     * artifactId-version-classifier.jar.
+     */
+    boolean containsJar(String artifactId, String version, String classifier) {
+        if (artifactId == null || version == null) {
+            return false;
+        }
+        String name = artifactId + "-" + version;
+        if (classifier != null && !classifier.isEmpty()) {
+            name += "-" + classifier;
+        }
+        return jarNames.contains(name + ".jar");
+    }
+
+    List<String> getEntries() {
+        return entries;
+    }
+}
diff --git 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/MavenDependencyDownloader.java
 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/MavenDependencyDownloader.java
index 977f37127ed8..097e6772d13a 100644
--- 
a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/MavenDependencyDownloader.java
+++ 
b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/download/MavenDependencyDownloader.java
@@ -17,8 +17,6 @@
 package org.apache.camel.main.download;
 
 import java.io.File;
-import java.lang.management.ManagementFactory;
-import java.lang.management.RuntimeMXBean;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URL;
@@ -31,6 +29,7 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Predicate;
 
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -63,9 +62,8 @@ public class MavenDependencyDownloader extends ServiceSupport 
implements Depende
     private static final String MINIMUM_QUARKUS_VERSION = "2.0.0";
 
     private static final Logger LOG = 
LoggerFactory.getLogger(MavenDependencyDownloader.class);
-    private static final String CP = System.getProperty("java.class.path");
 
-    private String[] bootClasspath;
+    private BootClasspath bootClasspath;
     private DownloadThreadPool threadPool;
     private boolean verbose;
     private ClassLoader classLoader;
@@ -300,13 +298,13 @@ public class MavenDependencyDownloader extends 
ServiceSupport implements Depende
         // when running jbang directly then the CP has some existing camel 
components
         // that essentially is not needed to be downloaded, but we need the 
listener to trigger
         // to capture that the GAV is required for running the application
-        if (CP != null) {
+        if (bootClasspath != null && sharesBootClasspath()) {
             // is it already on classpath
             String target = artifactId;
             if (version != null) {
                 target = target + "-" + version;
             }
-            if (CP.contains(target)) {
+            if (bootClasspath.contains(target)) {
                 // already on classpath
                 return;
             }
@@ -335,8 +333,13 @@ public class MavenDependencyDownloader extends 
ServiceSupport implements Depende
                 extraRepositories.addAll(resolveExtraRepositories(known));
             }
 
+            // what is already on the classpath is left out (with its 
dependencies), so it is not downloaded
+            Predicate<MavenGav> available = sharesBootClasspath()
+                    ? a -> bootClasspath != null && 
bootClasspath.containsJar(a.getArtifactId(), a.getVersion(),
+                            a.getClassifier())
+                    : null;
             List<MavenArtifact> artifacts = 
resolveDependenciesViaAether(parentGav, deps, extraRepositories,
-                    transitively, useApacheSnapshots);
+                    transitively, useApacheSnapshots, available);
             List<File> files = new ArrayList<>();
             if (verbose) {
                 LOG.info("Dependencies: {} -> [{}]", gav, artifacts);
@@ -517,24 +520,19 @@ public class MavenDependencyDownloader extends 
ServiceSupport implements Depende
             target = target + "-" + version;
         }
 
-        if (bootClasspath != null) {
-            for (String s : bootClasspath) {
-                if (s.contains(target)) {
-                    if (listener) {
-                        for (DownloadListener dl : downloadListeners) {
-                            dl.onDownloadDependency(groupId, artifactId, 
version);
-                        }
-                    }
-                    // already on classpath
-                    return true;
+        if (bootClasspath != null && sharesBootClasspath() && 
bootClasspath.contains(target)) {
+            if (listener) {
+                for (DownloadListener dl : downloadListeners) {
+                    dl.onDownloadDependency(groupId, artifactId, version);
                 }
             }
+            // already on classpath
+            return true;
         }
 
         if (classLoader instanceof URLClassLoader) {
-            // create path like target to match against the file url
-            String urlTarget = groupId + "/" + artifactId;
-            urlTarget = urlTarget.replace('.', '/');
+            // create path like target to match against the file url (the dots 
of the artifact id are kept)
+            String urlTarget = groupId.replace('.', '/') + "/" + artifactId;
             urlTarget += "/" + version + "/" + target + ".jar";
             urlTarget = FileUtil.normalizePath(urlTarget); // windows vs linux
             URLClassLoader ucl = (URLClassLoader) classLoader;
@@ -641,12 +639,27 @@ public class MavenDependencyDownloader extends 
ServiceSupport implements Depende
         mavenDownloader = mavenDownloaderImpl;
     }
 
+    /**
+     * Whether the class loader the JARs are added to sees the classpath the 
JVM was started with, so what is on it is
+     * not added (nor downloaded) again. A class loader without the 
application class loader as parent, such as for
+     * Hawtio, gets all the JARs.
+     */
+    private boolean sharesBootClasspath() {
+        if (classLoader == null) {
+            return true;
+        }
+        ClassLoader app = ClassLoader.getSystemClassLoader();
+        for (ClassLoader cl = classLoader; cl != null; cl = cl.getParent()) {
+            if (cl == app) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     protected void doInit() {
-        RuntimeMXBean mb = ManagementFactory.getRuntimeMXBean();
-        if (mb != null) {
-            bootClasspath = mb.getClassPath().split("[:|;]");
-        }
+        bootClasspath = 
BootClasspath.of(System.getProperty("java.class.path"));
         ServiceHelper.initService(versionResolver, threadPool, 
mavenDownloader);
     }
 
@@ -671,8 +684,20 @@ public class MavenDependencyDownloader extends 
ServiceSupport implements Depende
             String parentGav,
             List<String> depIds, Set<String> extraRepositories,
             boolean transitively, boolean useApacheSnapshots) {
+        return resolveDependenciesViaAether(parentGav, depIds, 
extraRepositories, transitively, useApacheSnapshots, null);
+    }
+
+    /**
+     * @param available the artifacts that are already available (such as on 
the classpath), which are left out together
+     *                  with their dependencies, or null to resolve them all
+     */
+    public List<MavenArtifact> resolveDependenciesViaAether(
+            String parentGav,
+            List<String> depIds, Set<String> extraRepositories,
+            boolean transitively, boolean useApacheSnapshots, 
Predicate<MavenGav> available) {
         try {
-            return mavenDownloader.resolveArtifacts(parentGav, depIds, 
extraRepositories, transitively, useApacheSnapshots);
+            return mavenDownloader.resolveArtifacts(parentGav, depIds, 
extraRepositories, transitively, useApacheSnapshots,
+                    available);
         } catch (MavenResolutionException e) {
             String repos = (e.getRepositories() == null || 
e.getRepositories().isEmpty())
                     ? "(empty URL list)"
diff --git 
a/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/BootClasspathTest.java
 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/BootClasspathTest.java
new file mode 100644
index 000000000000..436424058a56
--- /dev/null
+++ 
b/dsl/camel-kamelet-main/src/test/java/org/apache/camel/main/download/BootClasspathTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.main.download;
+
+import java.io.File;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.jar.Attributes;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class BootClasspathTest {
+
+    @TempDir
+    Path dir;
+
+    @Test
+    void jarsOfTheClasspath() {
+        String m2 = String.join(File.separator, "", "m2", "org", "apache", 
"camel", "camel-api", "4.24.0") + File.separator;
+        BootClasspath cp = BootClasspath.of(String.join(File.pathSeparator,
+                m2 + "camel-api-4.24.0.jar", "lib" + File.separator + 
"netty-transport-native-epoll-4.2.1-linux-x86_64.jar",
+                "target" + File.separator + "classes"));
+
+        assertTrue(cp.containsJar("camel-api", "4.24.0", null));
+        assertFalse(cp.containsJar("camel-api", "4.23.0", null));
+        // another artifact whose name ends with the same text
+        assertFalse(cp.containsJar("api", "4.24.0", null));
+        // a classifier is part of the name
+        assertTrue(cp.containsJar("netty-transport-native-epoll", "4.2.1", 
"linux-x86_64"));
+        assertFalse(cp.containsJar("netty-transport-native-epoll", "4.2.1", 
null));
+        assertFalse(cp.containsJar("camel-api", "4.24.0", "tests"));
+        assertTrue(cp.contains("camel-api-4.24.0"));
+        assertEquals(3, cp.getEntries().size());
+    }
+
+    @Test
+    void jarsOfTheManifestOfJavaJar() throws Exception {
+        // java -jar camel-launcher.jar: the other JARs are in the Class-Path 
of its manifest
+        Path lib = Files.createDirectories(dir.resolve("my lib"));
+        Manifest manifest = new Manifest();
+        manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, 
"1.0");
+        manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, 
"camel-api-4.24.0.jar camel-support-4.24.0.jar");
+        Path launcher = lib.resolve("camel-launcher-4.24.0.jar");
+        try (OutputStream os = Files.newOutputStream(launcher); 
JarOutputStream jar = new JarOutputStream(os, manifest)) {
+            jar.flush();
+        }
+
+        BootClasspath cp = BootClasspath.of(launcher.toString());
+
+        assertTrue(cp.containsJar("camel-launcher", "4.24.0", null));
+        assertTrue(cp.containsJar("camel-api", "4.24.0", null));
+        assertTrue(cp.containsJar("camel-support", "4.24.0", null));
+        
assertTrue(cp.getEntries().contains(lib.resolve("camel-support-4.24.0.jar").toString()));
+        assertTrue(cp.contains("camel-support-4.24.0"));
+    }
+
+    @Test
+    void noClasspath() {
+        BootClasspath cp = BootClasspath.of(null);
+
+        assertFalse(cp.containsJar("camel-api", "4.24.0", null));
+        assertFalse(cp.contains("camel-api"));
+    }
+}
diff --git 
a/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloader.java
 
b/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloader.java
index 480e171621c9..f808d83cd36d 100644
--- 
a/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloader.java
+++ 
b/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloader.java
@@ -18,6 +18,7 @@ package org.apache.camel.tooling.maven;
 
 import java.util.List;
 import java.util.Set;
+import java.util.function.Predicate;
 
 import org.apache.camel.Service;
 
@@ -62,6 +63,22 @@ public interface MavenDownloader extends Service {
             boolean transitively, boolean useApacheSnapshots)
             throws MavenResolutionException;
 
+    /**
+     * Resolves the artifacts as {@link #resolveArtifacts(String, List, Set, 
boolean, boolean)} does, leaving out the
+     * artifacts that are already available, such as the JARs on the 
classpath: they are not downloaded, nor are their
+     * dependencies.
+     *
+     * @param available the artifacts that are already available (tested with 
their base version), or null to resolve
+     *                  them all
+     */
+    default List<MavenArtifact> resolveArtifacts(
+            String rootGav,
+            List<String> dependencyGAVs, Set<String> extraRepositories,
+            boolean transitively, boolean useApacheSnapshots, 
Predicate<MavenGav> available)
+            throws MavenResolutionException {
+        return resolveArtifacts(rootGav, dependencyGAVs, extraRepositories, 
transitively, useApacheSnapshots);
+    }
+
     /**
      * Overload for {@link MavenDownloader#resolveArtifacts(String, List, Set, 
boolean, boolean)}
      */
diff --git 
a/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloaderImpl.java
 
b/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloaderImpl.java
index 65c7e3a87965..56dd570af226 100644
--- 
a/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloaderImpl.java
+++ 
b/tooling/camel-tooling-maven/src/main/java/org/apache/camel/tooling/maven/MavenDownloaderImpl.java
@@ -32,6 +32,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 import eu.maveniverse.maven.mima.context.Context;
@@ -58,6 +59,8 @@ import org.eclipse.aether.RepositorySystemSession;
 import org.eclipse.aether.artifact.Artifact;
 import org.eclipse.aether.artifact.DefaultArtifact;
 import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.collection.DependencyCollectionContext;
+import org.eclipse.aether.collection.DependencySelector;
 import org.eclipse.aether.graph.Dependency;
 import org.eclipse.aether.graph.DependencyFilter;
 import org.eclipse.aether.metadata.DefaultMetadata;
@@ -78,6 +81,7 @@ import org.eclipse.aether.resolution.MetadataRequest;
 import org.eclipse.aether.resolution.MetadataResult;
 import org.eclipse.aether.transfer.MetadataNotFoundException;
 import org.eclipse.aether.transfer.RepositoryOfflineException;
+import org.eclipse.aether.util.graph.selector.AndDependencySelector;
 import org.eclipse.aether.util.repository.SimpleArtifactDescriptorPolicy;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -577,14 +581,66 @@ public class MavenDownloaderImpl extends ServiceSupport 
implements MavenDownload
             List<String> dependencyGAVs, Set<String> extraRepositories,
             boolean transitively, boolean useApacheSnapshots)
             throws MavenResolutionException {
+        return resolveArtifacts(rootGav, dependencyGAVs, extraRepositories, 
transitively, useApacheSnapshots, null);
+    }
+
+    @Override
+    public List<MavenArtifact> resolveArtifacts(
+            String rootGav,
+            List<String> dependencyGAVs, Set<String> extraRepositories,
+            boolean transitively, boolean useApacheSnapshots, 
Predicate<MavenGav> available)
+            throws MavenResolutionException {
 
         // When preferLocal is enabled, what is already in the local 
repository is used as is: SNAPSHOT
         // metadata is not checked again remotely, while anything missing is 
still downloaded
         RepositorySystemSession session = preferLocal && !fresh ? 
getPreferLocalSession() : repositorySystemSession;
+        if (available != null && transitively) {
+            // leave out what is already available, with its dependencies, so 
it is not downloaded
+            DefaultRepositorySystemSession availableSession = new 
DefaultRepositorySystemSession(session);
+            
availableSession.setDependencySelector(AndDependencySelector.newInstance(
+                    session.getDependencySelector(), new 
AvailableDependencySelector(available)));
+            session = availableSession;
+        }
         return doResolveArtifacts(rootGav, dependencyGAVs, extraRepositories,
                 transitively, useApacheSnapshots, session);
     }
 
+    /**
+     * Leaves out the dependencies that are already available, such as on the 
classpath, together with their own
+     * dependencies.
+     */
+    static final class AvailableDependencySelector implements 
DependencySelector {
+
+        private final Predicate<MavenGav> available;
+
+        AvailableDependencySelector(Predicate<MavenGav> available) {
+            this.available = available;
+        }
+
+        @Override
+        public boolean selectDependency(Dependency dependency) {
+            Artifact a = dependency.getArtifact();
+            return !available.test(MavenGav.fromCoordinates(a.getGroupId(), 
a.getArtifactId(), a.getBaseVersion(),
+                    a.getExtension(), a.getClassifier()));
+        }
+
+        @Override
+        public DependencySelector 
deriveChildSelector(DependencyCollectionContext context) {
+            return this;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            // the collector caches by selector
+            return o instanceof AvailableDependencySelector other && available 
== other.available;
+        }
+
+        @Override
+        public int hashCode() {
+            return System.identityHashCode(available);
+        }
+    }
+
     private RepositorySystemSession getPreferLocalSession() {
         if (preferLocalSession == null) {
             DefaultRepositorySystemSession session = new 
DefaultRepositorySystemSession(repositorySystemSession);
diff --git 
a/tooling/camel-tooling-maven/src/test/java/org/apache/camel/tooling/maven/MavenDownloaderImplTest.java
 
b/tooling/camel-tooling-maven/src/test/java/org/apache/camel/tooling/maven/MavenDownloaderImplTest.java
index bca14460d7e1..599983902a36 100644
--- 
a/tooling/camel-tooling-maven/src/test/java/org/apache/camel/tooling/maven/MavenDownloaderImplTest.java
+++ 
b/tooling/camel-tooling-maven/src/test/java/org/apache/camel/tooling/maven/MavenDownloaderImplTest.java
@@ -994,6 +994,57 @@ class MavenDownloaderImplTest {
         return repo;
     }
 
+    @Test
+    void testAvailableArtifactsAreLeftOutWithTheirDependencies() throws 
Exception {
+        // app -> lib-a -> lib-a-dep, and app -> lib-b
+        File repo = new File(tempDir, "available-repo");
+        writeJar(repo, "app", dependencies("lib-a", "lib-b"));
+        writeJar(repo, "lib-a", dependencies("lib-a-dep"));
+        writeJar(repo, "lib-a-dep", "");
+        writeJar(repo, "lib-b", "");
+        File localRepo = new File(tempDir, "available-local-repo");
+
+        try (MavenDownloaderImpl downloader = new MavenDownloaderImpl()) {
+            downloader.setMavenCentralEnabled(false);
+            downloader.setMavenApacheSnapshotEnabled(false);
+            downloader.setMavenSettingsLocation("false");
+            downloader.setRepos(repo.toURI().toString());
+            downloader.build();
+            MavenDownloader customDownloader = 
downloader.customize(localRepo.getAbsolutePath(), 5000, 5000);
+
+            // lib-a is available, such as on the classpath
+            List<MavenArtifact> artifacts = 
customDownloader.resolveArtifacts(null,
+                    List.of("org.example.fixture:app:1.0.0"), null, true, 
false,
+                    gav -> "lib-a".equals(gav.getArtifactId()) && 
"1.0.0".equals(gav.getVersion()));
+
+            assertEquals(List.of("org.example.fixture:app:1.0.0", 
"org.example.fixture:lib-b:1.0.0"),
+                    artifacts.stream().map(a -> 
a.getGav().toString()).toList());
+            // not downloaded, not even the POM of lib-a
+            assertFalse(new File(localRepo, 
"org/example/fixture/lib-a").exists());
+            assertFalse(new File(localRepo, 
"org/example/fixture/lib-a-dep").exists());
+
+            // without it, all are resolved
+            artifacts = customDownloader.resolveArtifacts(null, 
List.of("org.example.fixture:app:1.0.0"), null, true,
+                    false, null);
+            assertEquals(4, artifacts.size());
+        }
+    }
+
+    private static String dependencies(String... artifactIds) {
+        StringBuilder sb = new StringBuilder("  <dependencies>\n");
+        for (String artifactId : artifactIds) {
+            sb.append("    
<dependency><groupId>org.example.fixture</groupId><artifactId>").append(artifactId)
+                    
.append("</artifactId><version>1.0.0</version></dependency>\n");
+        }
+        return sb.append("  </dependencies>\n").toString();
+    }
+
+    private static void writeJar(File repo, String artifactId, String body) 
throws Exception {
+        writePom(repo, artifactId, body);
+        File dir = new File(repo, "org/example/fixture/" + artifactId + 
"/1.0.0");
+        Files.writeString(new File(dir, artifactId + "-1.0.0.jar").toPath(), 
artifactId);
+    }
+
     private static void writePom(File repo, String artifactId, String body) 
throws Exception {
         File dir = new File(repo, "org/example/fixture/" + artifactId + 
"/1.0.0");
         assertTrue(dir.mkdirs() || dir.isDirectory());

Reply via email to