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());