/*
* Copyright to the original author or authors.
*
* Licensed 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.rioproject.resolver.aether;
import org.apache.maven.settings.building.SettingsBuildingException;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.collection.DependencyCollectionException;
import org.eclipse.aether.repository.ArtifactRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.resolution.*;
import org.rioproject.resolver.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Uses Maven 3's native dependency resolution interface, Aether.
*
* @author Dennis Reedy
*/
public class AetherResolver implements Resolver, SettableResolver {
protected AetherService service;
private final Map<ResolutionRequest, Future<String[]>> resolvingMap = new ConcurrentHashMap<ResolutionRequest, Future<String[]>>();
private final ExecutorService resolverExecutor;
private final List<RemoteRepository> cachedRemoteRepositories = new ArrayList<RemoteRepository>();
private final FlatDirectoryReader flatDirectoryReader = new FlatDirectoryWorkspaceReader();
private static final Logger logger = LoggerFactory.getLogger(AetherResolver.class.getName());
public AetherResolver() {
resolverExecutor = Executors.newCachedThreadPool(new ThreadFactory() {
@Override public Thread newThread(Runnable runnable) {
Thread thread = Executors.defaultThreadFactory().newThread(runnable);
thread.setDaemon(true);
return thread;
}
});
service = AetherService.getDefaultInstance();
}
/**
* {@inheritDoc}
*/
@Override
public String[] getClassPathFor(String artifact) throws ResolverException {
String[] classPath;
Future<String[]> future;
ResolutionRequest request = new ResolutionRequest(artifact);
synchronized (resolvingMap) {
future = resolvingMap.get(request);
if(future==null) {
future = resolverExecutor.submit(new ResolvingRequestTask(request));
resolvingMap.put(request, future);
if(logger.isDebugEnabled()) {
logger.debug(String.format("Created and set new ResolvingTask for %s", artifact));
}
} else {
request = getResolutionRequest(request);
}
}
request.increment();
try {
classPath = future.get();
} catch (InterruptedException e) {
throw new ResolverException(String.format("While trying to resolve %s", artifact), e);
} catch (ExecutionException e) {
throw new ResolverException(String.format("While trying to resolve %s", artifact), e);
} finally {
if(request.decrement()==0) {
resolvingMap.remove(request);
}
}
return classPath;
}
@Override
public String[] getClassPathFor(String artifact, File pom, boolean download) throws ResolverException {
return getClassPathFor(artifact);
}
/**
* {@inheritDoc}
*/
@Override
public String[] getClassPathFor(String artifact, RemoteRepository[] repositories) throws ResolverException {
String[] classPath;
Future<String[]> future;
ResolutionRequest request = new ResolutionRequest(artifact, repositories);
synchronized (resolvingMap) {
future = resolvingMap.get(request);
if(future==null) {
future = resolverExecutor.submit(new ResolvingRequestTask(request));
resolvingMap.put(request, future);
if(logger.isDebugEnabled()) {
logger.debug("Created and set new ResolvingRequestTask for {} with repositories {}",
artifact, repositories);
}
} else {
request = getResolutionRequest(request);
}
}
request.increment();
try {
classPath = future.get();
} catch (InterruptedException e) {
throw new ResolverException(String.format("While trying to resolve %s", artifact), e);
} catch (ExecutionException e) {
throw new ResolverException(String.format("While trying to resolve %s", artifact), e);
} finally {
if(request.decrement()==0) {
resolvingMap.remove(request);
}
}
return classPath;
}
/**
* {@inheritDoc}
*/
@Override
public URL getLocation(String artifact, String artifactType) throws ResolverException {
URL location;
try {
location = service.getLocation(artifact, artifactType);
} catch (ArtifactResolutionException e) {
location = getURLFromFlatDirs(artifact, artifactType);
if(location==null)
throw new ResolverException(String.format("Error locating %s: %s", artifact, e.getLocalizedMessage()));
} catch (MalformedURLException e) {
throw new ResolverException(String.format("Error creating URL for resolved artifact %s: %s",
artifact, e.getLocalizedMessage()));
} catch (SettingsBuildingException e) {
throw new ResolverException(String.format("Error loading settings for resolved artifact %s: %s",
artifact, e.getLocalizedMessage()));
} catch (VersionRangeResolutionException e) {
throw new ResolverException(String.format("Error resolving latest version for %s: %s",
artifact, e.getLocalizedMessage()));
}
return location;
}
private URL getURLFromFlatDirs(String artifact, String artifactType) throws ResolverException {
DefaultArtifact a = new DefaultArtifact(artifact);
String extension = artifactType==null? "jar":artifactType;
DefaultArtifact toResolve = new DefaultArtifact(a.getGroupId(),
a.getArtifactId(),
a.getClassifier(),
extension,
a.getVersion());
ArtifactResult result = flatDirectoryReader.findArtifact(toResolve);
if(result!=null) {
try {
return result.getArtifact().getFile().toURI().toURL();
} catch (MalformedURLException e) {
throw new ResolverException(String.format("Error creating URL for resolved artifact %s: %s",
artifact, e.getLocalizedMessage()));
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public URL getLocation(String artifact, String artifactType, RemoteRepository[] repositories) throws ResolverException {
URL location;
try {
List<org.eclipse.aether.repository.RemoteRepository> remoteRepositories =
transformRemoteRepository(repositories);
location = service.getLocation(artifact, artifactType, remoteRepositories);
} catch (ArtifactResolutionException e) {
location = getURLFromFlatDirs(artifact, artifactType);
if(location==null)
throw new ResolverException(String.format("Error locating %s: %s", artifact, e.getLocalizedMessage()));
} catch (MalformedURLException e) {
throw new ResolverException(String.format("Error creating URL for resolved artifact %s: %s",
artifact, e.getLocalizedMessage()));
} catch (SettingsBuildingException e) {
throw new ResolverException(String.format("Error loading settings for resolved artifact %s: %s",
artifact, e.getLocalizedMessage()));
} catch (VersionRangeResolutionException e) {
throw new ResolverException(String.format("Error resolving latest version for %s: %s",
artifact, e.getLocalizedMessage()));
}
return location;
}
/**
* {@inheritDoc}
*/
@Override
public SettableResolver setRemoteRepositories(Collection<RemoteRepository> repositories) {
service.setConfiguredRepositories(transformRemoteRepository(repositories.toArray(new RemoteRepository[repositories.size()])));
return this;
}
/**
* {@inheritDoc}
*/
@Override public SettableResolver setFlatDirectories(Collection<File> directories) {
flatDirectoryReader.addDirectories(directories);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public Collection<RemoteRepository> getRemoteRepositories() {
List<org.eclipse.aether.repository.RemoteRepository> repos = service.getRemoteRepositories();
List<RemoteRepository> remoteRepositories = new ArrayList<RemoteRepository>();
for(org.eclipse.aether.repository.RemoteRepository r : repos)
remoteRepositories.add(transformAetherRemoteRepository(r));
for(RemoteRepository rr : cachedRemoteRepositories) {
if(!remoteRepositories.contains(rr))
remoteRepositories.add(rr);
}
return remoteRepositories;
}
public AetherService getAetherService() {
return service;
}
protected List<org.eclipse.aether.repository.RemoteRepository> transformRemoteRepository(RemoteRepository[] repositories) {
if(repositories==null)
throw new IllegalArgumentException("repositories must not be null");
List<org.eclipse.aether.repository.RemoteRepository> remoteRepositories =
new ArrayList<org.eclipse.aether.repository.RemoteRepository>();
for(RemoteRepository rr : repositories) {
RepositoryPolicy releasePolicy = new RepositoryPolicy(true,
rr.getReleaseUpdatePolicy(),
rr.getReleaseChecksumPolicy());
RepositoryPolicy snapshotPolicy = new RepositoryPolicy(true,
rr.getSnapshotUpdatePolicy(),
rr.getSnapshotChecksumPolicy());
org.eclipse.aether.repository.RemoteRepository.Builder repoBuilder =
new org.eclipse.aether.repository.RemoteRepository.Builder(rr.getId(), "default", rr.getUrl());
remoteRepositories.add(repoBuilder
.setSnapshotPolicy(snapshotPolicy)
.setReleasePolicy(releasePolicy)
.build());
}
return remoteRepositories;
}
protected String[] produceClassPathFromResolutionResult(ResolutionResult result) {
List<String> classPath = new ArrayList<String>();
for (ArtifactResult artifactResult : result.getArtifactResults()) {
if(artifactResult.getArtifact()==null) {
logger.error("Unknown artifact for {}", artifactResult.getRequest().getArtifact());
}
if(logger.isDebugEnabled()) {
if(artifactResult.getArtifact()!=null)
logger.debug("Adding classpath for artifact: {}, result: {}",
artifactResult.getArtifact(), artifactResult.getArtifact().getFile());
else {
logger.error("Adding classpath for artifact: {}, no file found",
artifactResult.getArtifact());
}
}
classPath.add(artifactResult.getArtifact().getFile().getAbsolutePath());
ArtifactRepository r = artifactResult.getRepository();
if(r instanceof org.eclipse.aether.repository.RemoteRepository) {
RemoteRepository rr = transformAetherRemoteRepository((org.eclipse.aether.repository.RemoteRepository)r);
if(!cachedRemoteRepositories.contains(rr))
cachedRemoteRepositories.add(rr);
}
}
if(logger.isDebugEnabled())
logResolutionResult(result);
return classPath.toArray(new String[classPath.size()]);
}
protected void logResolutionResult(ResolutionResult result) {
StringBuilder resolvedList = new StringBuilder();
int artifactLength = getMaxArtifactStringLength(result.getArtifactResults());
for (ArtifactResult artifactResult : result.getArtifactResults() ) {
if(resolvedList.length()>0)
resolvedList.append("\n");
resolvedList.append(" ").append(String.format("%-"+artifactLength+"s", artifactResult.getArtifact()));
resolvedList.append(" resolved to ").append(artifactResult.getArtifact().getFile());
}
String newLine = "";
if(resolvedList.length()==0)
resolvedList.append(" <No artifacts resolved>");
else
newLine = "\n";
logger.debug(String.format("Artifact resolution for %s:%s", result.getArtifact(), newLine+resolvedList.toString()));
}
protected RemoteRepository transformAetherRemoteRepository(org.eclipse.aether.repository.RemoteRepository r) {
RemoteRepository rr = new RemoteRepository();
rr.setId(r.getId());
rr.setUrl(r.getUrl());
rr.setReleaseChecksumPolicy(r.getPolicy(false).getChecksumPolicy());
rr.setReleaseUpdatePolicy(r.getPolicy(false).getUpdatePolicy());
rr.setSnapshotChecksumPolicy(r.getPolicy(true).getChecksumPolicy());
rr.setSnapshotUpdatePolicy(r.getPolicy(true).getUpdatePolicy());
rr.setReleases(r.getPolicy(true).isEnabled());
rr.setSnapshots(r.getPolicy(false).isEnabled());
return rr;
}
private int getMaxArtifactStringLength(List<ArtifactResult> artifactResults) {
int artifactLength = 0;
for (ArtifactResult artifactResult : artifactResults ) {
artifactLength = artifactResult.getArtifact().toString().length()>artifactLength?
artifactResult.getArtifact().toString().length():artifactLength;
}
return artifactLength;
}
private ResolutionRequest getResolutionRequest(ResolutionRequest r) {
ResolutionRequest request = null;
for(Map.Entry<ResolutionRequest, Future<String[]>> entry : resolvingMap.entrySet()) {
if(entry.getKey().equals(r)) {
request = entry.getKey();
break;
}
}
return request==null?r : request;
}
/**
* Asynchronous task for resolving an artifact
*/
private class ResolvingRequestTask implements Callable<String[]> {
private ResolutionRequest request;
private ResolvingRequestTask(ResolutionRequest request) {
this.request = request;
}
public String[] call() throws ResolverException {
String[] classPath;
List<org.eclipse.aether.repository.RemoteRepository> remoteRepositories = null;
if(request.getRepositories()!=null) {
remoteRepositories = transformRemoteRepository(request.getRepositories());
}
try {
ResolutionResult result;
if(remoteRepositories!=null) {
Artifact a = new Artifact(request.getArtifact());
result = service.resolve(a.getGroupId(),
a.getArtifactId(),
a.getType(),
a.getClassifier(),
a.getVersion(),
remoteRepositories);
} else {
DefaultArtifact a = new DefaultArtifact(request.getArtifact());
result = service.resolve(a.getGroupId(),
a.getArtifactId(),
a.getExtension(),
a.getClassifier(),
a.getVersion());
}
classPath = produceClassPathFromResolutionResult(result);
} catch (SettingsBuildingException e) {
throw new ResolverException(String.format("Error reading local Maven configuration: %s",
e.getLocalizedMessage()), e);
} catch (DependencyCollectionException e) {
CollectRequest collectRequest = e.getResult().getRequest();
ArtifactResult artifactResult = null;
if(collectRequest.getRoot()!=null && collectRequest.getRoot().getArtifact()!=null)
artifactResult = flatDirectoryReader.findArtifact(collectRequest.getRoot().getArtifact());
if(artifactResult==null) {
throw new ResolverException("Encountered bad artifact descriptors, version ranges or " +
"other issues during calculation of the dependency " +
"graph",
e);
} else {
ResolutionResult result = new ResolutionResult()
.setArtifact(new DefaultArtifact(request.getArtifact()))
.addArtifactResult(artifactResult);
classPath = produceClassPathFromResolutionResult(result);
}
} catch (DependencyResolutionException e) {
List<ArtifactResult> artifactResults = new ArrayList<ArtifactResult>();
Set<org.eclipse.aether.artifact.Artifact> flatDirArtifacts = new HashSet<org.eclipse.aether.artifact.Artifact>();
for(ArtifactResult result : e.getResult().getArtifactResults()) {
if(result.isMissing()) {
flatDirArtifacts.add(result.getRequest().getArtifact());
} else {
artifactResults.add(result);
}
}
//artifactResults.addAll(e.getResult().getArtifactResults());
for(Exception collectException : e.getResult().getCollectExceptions()) {
if(collectException instanceof ArtifactDescriptorException) {
flatDirArtifacts.add(((ArtifactDescriptorException)collectException).getResult().getArtifact());
}
}
int toResolveLocally =flatDirArtifacts.size();
if(logger.isDebugEnabled())
logger.debug("Try and resolve {} artifacts using configured flatDirs", toResolveLocally);
for(org.eclipse.aether.artifact.Artifact artifact : flatDirArtifacts) {
ArtifactResult artifactResult = flatDirectoryReader.findArtifact(artifact);
if (artifactResult != null) {
toResolveLocally--;
artifactResults.add(artifactResult);
}
}
if(logger.isDebugEnabled())
logger.debug("Number of unresolved artifact after flatDir check: {}", toResolveLocally);
if(toResolveLocally==0) {
ResolutionResult result = new ResolutionResult(new DefaultArtifact(request.getArtifact()), artifactResults);
classPath = produceClassPathFromResolutionResult(result);
} else {
throw new ResolverException(String.format("Could not download all transitive dependencies: %s",
e.getLocalizedMessage()));
}
} catch (VersionRangeResolutionException e) {
throw new ResolverException(String.format("Error resolving latest version: %s",
e.getLocalizedMessage()), e);
}
return classPath;
}
}
class ResolutionRequest {
private String artifact;
private RemoteRepository[] repositories;
private AtomicInteger counter = new AtomicInteger(0);
ResolutionRequest(String artifact) {
this.artifact = artifact;
}
ResolutionRequest(String artifact, RemoteRepository[] repositories) {
this.artifact = artifact;
this.repositories = repositories;
}
String getArtifact() {
return artifact;
}
RemoteRepository[] getRepositories() {
return repositories;
}
void increment() {
counter.incrementAndGet();
}
Integer decrement() {
return counter.decrementAndGet();
}
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ResolutionRequest that = (ResolutionRequest) o;
return artifact.equals(that.artifact) && Arrays.equals(repositories, that.repositories);
}
@Override
public int hashCode() {
int result = artifact.hashCode();
result = 31 * result + (repositories != null ? Arrays.hashCode(repositories) : 0);
return result;
}
}
}