/*
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.test.runner.api;
import static com.google.common.base.Joiner.on;
import static java.util.stream.Collectors.toList;
import static org.eclipse.aether.util.artifact.ArtifactIdUtils.toId;
import static org.mule.runtime.api.util.Preconditions.checkNotNull;
import java.io.File;
import java.util.List;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.collection.DependencyCollectionException;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactDescriptorException;
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
import org.eclipse.aether.resolution.ArtifactDescriptorResult;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResolutionException;
import org.eclipse.aether.util.filter.PatternInclusionsDependencyFilter;
import org.eclipse.aether.util.graph.visitor.PathRecordingDependencyVisitor;
import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides {@link Dependency}s resolutions for Maven {@link Artifact} based on {@link RepositorySystem} and
* {@link RepositorySystemSession} from Eclipse Aether.
*
* @since 4.0
*/
public class DependencyResolver {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
private RepositorySystem system;
private RepositorySystemSession session;
private List<RemoteRepository> remoteRepositories;
/**
* Creates an instance of the resolver.
* @param system {@link RepositorySystem} where {@link Dependency}s will be resolved.
* @param session {@link RepositorySystemSession} used to resolve and {@link Dependency}s.
* @param remoteRepositories {@link RemoteRepository} to be used for resolving dependencies.
*/
public DependencyResolver(RepositorySystem system, RepositorySystemSession session, List<RemoteRepository> remoteRepositories) {
checkNotNull(system, "system cannot be null");
checkNotNull(session, "session cannot be null");
this.system = system;
this.session = session;
this.remoteRepositories = remoteRepositories;
}
/**
* Gets information about an artifact like its direct dependencies and potential relocations.
*
* @param artifact the {@link Artifact} requested, must not be {@code null}
* @return {@link ArtifactDescriptorResult} descriptor result, never {@code null}
* @throws {@link ArtifactDescriptorException} if the artifact descriptor could not be read
*/
public ArtifactDescriptorResult readArtifactDescriptor(Artifact artifact) throws ArtifactDescriptorException {
checkNotNull(artifact, "artifact cannot be null");
final ArtifactDescriptorRequest request =
new ArtifactDescriptorRequest(artifact, remoteRepositories, null);
return system.readArtifactDescriptor(session, request);
}
/**
* Resolves the path for an artifact.
*
* @param artifact the {@link Artifact} requested, must not be {@code null}
* @return The resolution result, never {@code null}.
* @throws {@link ArtifactResolutionException} if the artifact could not be resolved.
*/
public ArtifactResult resolveArtifact(Artifact artifact) throws ArtifactResolutionException {
checkNotNull(artifact, "artifact cannot be null");
final ArtifactRequest request = new ArtifactRequest(artifact, remoteRepositories, null);
return system.resolveArtifact(session, request);
}
/**
* Resolves direct dependencies for an {@link Artifact}.
*
* @param artifact {@link Artifact} to collect its direct dependencies
* @return a {@link List} of {@link Dependency} for each direct dependency resolved
* @throws {@link ArtifactDescriptorException} if the artifact descriptor could not be read
*/
public List<Dependency> getDirectDependencies(Artifact artifact) throws ArtifactDescriptorException {
checkNotNull(artifact, "artifact cannot be null");
return readArtifactDescriptor(artifact).getDependencies();
}
/**
* Resolves and filters transitive dependencies for the root and direct dependencies.
* <p/>
* If both a root dependency and direct dependencies are given, the direct dependencies will be merged with the direct
* dependencies from the root dependency's artifact descriptor, giving higher priority to the dependencies from the root.
*
* @param root {@link Dependency} node from to collect its dependencies, may be {@code null}
* @param directDependencies {@link List} of direct {@link Dependency} to collect its transitive dependencies, may be
* {@code null}
* @param managedDependencies {@link List} of managed {@link Dependency}s to be used for resolving the depedency graph, may be
* {@code null}
* @param dependencyFilter {@link DependencyFilter} to include/exclude dependency nodes during collection and resolve operation.
* May be {@code null} to no filter
* @return a {@link List} of {@link File}s for each dependency resolved
* @throws {@link DependencyCollectionException} if the dependency tree could not be built
* @thwows {@link DependencyResolutionException} if the dependency tree could not be built or any dependency artifact could not
* be resolved
*/
public List<File> resolveDependencies(Dependency root, List<Dependency> directDependencies,
List<Dependency> managedDependencies,
DependencyFilter dependencyFilter)
throws DependencyCollectionException, DependencyResolutionException {
CollectRequest collectRequest = new CollectRequest();
collectRequest.setRoot(root);
collectRequest.setDependencies(directDependencies);
collectRequest.setManagedDependencies(managedDependencies);
collectRequest.setRepositories(remoteRepositories);
DependencyNode node;
try {
node = system.collectDependencies(session, collectRequest).getRoot();
logDependencyGraph(node, collectRequest);
DependencyRequest dependencyRequest = new DependencyRequest();
dependencyRequest.setRoot(node);
dependencyRequest.setCollectRequest(collectRequest);
if (dependencyFilter != null) {
dependencyRequest.setFilter(dependencyFilter);
}
node = system.resolveDependencies(session, dependencyRequest).getRoot();
} catch (DependencyResolutionException e) {
logger.warn("Dependencies couldn't be resolved for request '{}', {}", collectRequest, e.getMessage());
node = e.getResult().getRoot();
logUnresolvedArtifacts(node, e);
throw e;
}
List<File> files = getFiles(node);
return files;
}
private void logDependencyGraph(DependencyNode node, Object request) {
if (logger.isTraceEnabled()) {
PathRecordingDependencyVisitor visitor = new PathRecordingDependencyVisitor(null, false);
node.accept(visitor);
logger.trace("******* Dependency Graph calculated for {} with request: '{}' *******", request.getClass().getSimpleName(),
request);
visitor.getPaths().stream().forEach(
pathList -> logger.trace(on(" -> ")
.join(pathList.stream().filter(path -> path != null).collect(toList()))));
logger.trace("******* End of dependency Graph *******");
}
}
/**
* Traverse the {@link DependencyNode} to get the files for each artifact.
*
* @param node {@link DependencyNode} that represents the dependency graph
* @return {@link List} of {@link File}s for each artifact resolved
*/
private List<File> getFiles(DependencyNode node) {
PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
node.accept(nlg);
return nlg.getFiles().stream().map(File::getAbsoluteFile).collect(toList());
}
/**
* Logs the paths for each dependency not found
*
* @param node root {@link DependencyNode}, can be a "null" root (imaginary root)
* @param e {@link DependencyResolutionException} the error to collect paths.
*/
private void logUnresolvedArtifacts(DependencyNode node, DependencyResolutionException e) {
List<ArtifactResult> artifactResults = e.getResult().getArtifactResults().stream()
.filter(artifactResult -> !artifactResult.getExceptions().isEmpty()).collect(toList());
final List<String> patternInclusion =
artifactResults.stream().map(artifactResult -> toId(artifactResult.getRequest().getArtifact())).collect(toList());
PathRecordingDependencyVisitor visitor =
new PathRecordingDependencyVisitor(new PatternInclusionsDependencyFilter(patternInclusion), node.getArtifact() != null);
node.accept(visitor);
visitor.getPaths().stream().forEach(path -> {
List<DependencyNode> unresolvedArtifactPath =
path.stream().filter(dependencyNode -> dependencyNode.getArtifact() != null).collect(toList());
if (!unresolvedArtifactPath.isEmpty()) {
logger.warn("Dependency path to not resolved artifacts -> {}", unresolvedArtifactPath.toString());
}
});
}
}