package net.sourceforge.javautil.classloader.resolver.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import net.sourceforge.javautil.classloader.resolver.ClassPackageResolverNetworkNode.Type;
import net.sourceforge.javautil.classloader.resolver.IClassArtifactReference;
import net.sourceforge.javautil.classloader.resolver.IClassPackage;
import net.sourceforge.javautil.classloader.resolver.IClassPackageDependencyReference;
import net.sourceforge.javautil.classloader.resolver.IClassPackageDescriptor;
import net.sourceforge.javautil.classloader.resolver.IClassPackageReference;
import net.sourceforge.javautil.classloader.resolver.IClassPackageRepositoryLocal;
import net.sourceforge.javautil.classloader.resolver.IClassPackageRepositoryLocalImportable;
import net.sourceforge.javautil.classloader.resolver.IClassPackageRepositoryRemote;
import net.sourceforge.javautil.classloader.resolver.IClassPackageRepositoryRemoteAttempt;
import net.sourceforge.javautil.classloader.resolver.IClassPackageResolver;
import net.sourceforge.javautil.classloader.resolver.ClassPackageResolverContext;
import net.sourceforge.javautil.classloader.resolver.ClassPackageResolverContext;
import net.sourceforge.javautil.classloader.resolver.ClassPackageResolverNetworkNode;
import net.sourceforge.javautil.classloader.resolver.ClassPackageUpgradeException;
import net.sourceforge.javautil.classloader.resolver.IClassDependency.Scope;
import net.sourceforge.javautil.classloader.resolver.IClassPackage.IVersion;
import net.sourceforge.javautil.classloader.resolver.impl.maven.MavenRepositoryRemote;
import net.sourceforge.javautil.classloader.resolver.impl.maven.ProjectObjectModel;
import net.sourceforge.javautil.classloader.source.ClassSource;
import net.sourceforge.javautil.classloader.source.VirtualDirectoryClassSource;
import net.sourceforge.javautil.classloader.source.ZipClassSource;
import net.sourceforge.javautil.classloader.util.ClassPackageUtil;
import net.sourceforge.javautil.common.ArchiveUtil;
import net.sourceforge.javautil.common.StringUtil;
import net.sourceforge.javautil.common.exception.ThrowableManagerRegistry;
import net.sourceforge.javautil.common.io.IVirtualDirectory;
import net.sourceforge.javautil.common.io.impl.ISystemArtifact;
import net.sourceforge.javautil.common.io.impl.SystemDirectory;
import net.sourceforge.javautil.common.io.impl.SystemFile;
import net.sourceforge.javautil.common.logging.ILogger;
import net.sourceforge.javautil.common.logging.LoggingContext;
import net.sourceforge.javautil.common.reflection.cache.ClassDescriptor;
/**
* The default resolver implementation for {@link IClassPackage} resolution.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: ClassPackageResolverImpl.java 2745 2011-02-05 04:23:36Z ponderator $
*/
public class ClassPackageResolverImpl implements IClassPackageResolver {
private static final ILogger log = LoggingContext.getContextualLogger(ClassPackageResolverImpl.class);
/**
* The main local repository, that has the capability of having packages imported into it.
*/
protected final IClassPackageRepositoryLocalImportable repository;
protected final List<IClassPackageRepositoryLocal> locals = new ArrayList<IClassPackageRepositoryLocal>();
protected final Map<String, IClassPackageRepositoryRemote> remote = new HashMap<String, IClassPackageRepositoryRemote>();
protected final Map<String, IClassPackage> cache = new HashMap<String, IClassPackage>();
protected long remoteAttemptInterval = 24 * (60 * 60) * 1000;
public ClassPackageResolverImpl(IClassPackageRepositoryLocalImportable repository) {
this.repository = repository;
}
/**
* Add a local repository for resolution only.
*
* @param local The local repository to add
*/
public void addLocalRepository (IClassPackageRepositoryLocal local) { this.locals.add(local); }
public List<IClassPackageRepositoryLocal> getLocalRepositories() {
List<IClassPackageRepositoryLocal> local = new ArrayList<IClassPackageRepositoryLocal>(this.locals);
local.add(0, repository);
return local;
}
public List<IClassPackageRepositoryRemote> getRemoteRepositories() {
return new ArrayList<IClassPackageRepositoryRemote>(this.remote.values());
}
public IClassPackageRepositoryLocalImportable getImportableRepository () {
return this.repository;
}
/**
* Add a remote repository for resolving locally unavailable packages.
*
* @param remote The remote repository to add
*/
public void addRemoteRepository (IClassPackageRepositoryRemote remote) { this.remote.put(remote.getName(), remote); }
public IVersion getLatestVersion(IClassArtifactReference reference, boolean localOnly) {
List<IVersion> versions = this.getVersionsAvailable(reference, localOnly);
return versions.size() == 0 ? null : versions.get(versions.size() - 1);
}
public List<IVersion> getVersionsAvailable(IClassArtifactReference reference, boolean localOnly) {
List<IVersion> versions = new ArrayList<IVersion>();
if (repository.contains(this, reference))
versions.addAll( repository.getVersionsAvailable(reference) );
for (IClassPackageRepositoryLocal local : this.locals) {
versions.addAll( local.getVersionsAvailable(reference) );
}
if (!localOnly)
for (String name : new ArrayList<String>(remote.keySet())) {
IClassPackageRepositoryRemote remote = this.remote.get(name);
if (remote.contains(reference))
versions.addAll( remote.getVersionsAvailable(reference) );
}
Collections.sort(versions);
return versions;
}
public boolean contains(IClassPackageReference reference, boolean localOnly) {
if (repository.contains(this, reference)) return true;
for (IClassPackageRepositoryLocal local : this.locals) {
if (local.contains(this, reference)) return true;
}
if (!localOnly)
for (String name : new ArrayList<String>(remote.keySet())) {
IClassPackageRepositoryRemote remote = this.remote.get(name);
if (remote.contains(reference)) return true;
}
return false;
}
@Override public IClassPackage locate(IClassPackageReference reference) {
ClassPackageResolverContext ctx = this.resolve(new ClassPackageResolverContext(reference));
ClassPackageResolverNetworkNode node = ctx.getResolved(reference);
return node.getType() == Type.Available ? node.createPackage() : null;
}
public ClassPackageResolverContext resolve(IClassPackageDependencyReference reference, IClassPackageDescriptor descriptor) {
return this.resolve(new ClassPackageResolverContext().pushNode(reference, descriptor)).popNode();
}
public ClassPackageResolverContext resolve(ClassPackageResolverContext context, IClassPackageDependencyReference reference) {
return this.resolve(context.pushNode(reference, context.getDescriptor())).popNode();
}
@Override public IClassPackage getPackage(IClassPackageReference reference) {
ClassPackageResolverContext ctx = new ClassPackageResolverContext(reference);
ctx.setResolveDependencies(false);
this.resolve(ctx);
ClassPackageResolverNetworkNode node = ctx.getResolved(reference);
if (node != null) {
ClassPackageUtil.downloadDependencies(this, node);
return node.createPackage();
}
return null;
}
@Override public IClassPackageDescriptor getDescriptor(IClassPackageReference reference) {
ClassPackageResolverContext ctx = new ClassPackageResolverContext(reference);
ctx.setResolveDependencies(false);
this.resolve(ctx);
ClassPackageResolverNetworkNode node = ctx.getResolved(reference);
return node == null ? null : node.getDescriptor();
}
public ClassPackageResolverContext resolve(ClassPackageResolverContext ctx) {
if ("net.sourceforge.javautil".equals( ctx.getCurrentDependency().getGroupId() )) {
if ("classloader".equals( ctx.getCurrentDependency().getArtifactId() ) || "common".equals( ctx.getCurrentDependency().getArtifactId() )) {
if (log.isDebug())
log.debug("Ignoring implied reference: " + ctx.getCurrentDependency());
return ctx;
}
}
ClassPackageResolverNetworkNode node = ctx.getResolved(ctx.getCurrentDependency());
if (node != null) {
if (node.getReference().getVersion().compareTo(ctx.getCurrentDependency().getVersion()) < 0 && !node.isPrimary()) {
log.warn("Upgrading from: " + node.getReference() + " to " + ctx.getCurrentDependency());
ctx.remove(node);
} else {
ctx.relate(node);
return ctx;
}
}
if (ctx.getCurrentDependency().isSystemReference()) {
ISystemArtifact artifact = ctx.getCurrentDependency().getSystemArtifact();
ctx.setAvailable(this, null, artifact instanceof SystemDirectory ?
new VirtualDirectoryClassSource((IVirtualDirectory)artifact) :
new ZipClassSource(artifact.getRealArtifact()), ctx.getCurrentDependency().getSystemDescriptor());
return ctx;
}
if (!this.resolveInternally(ctx)) {
Map<String, IClassPackageRepositoryRemote> remoteRepositories = new LinkedHashMap<String, IClassPackageRepositoryRemote>();
for (String name : this.remote.keySet()) {
IClassPackageRepositoryRemote rr = remote.get(name);
remoteRepositories.put(rr.getRepositoryURL().toExternalForm(), rr);
}
Set<IClassPackageDescriptor> cpdrs = ctx.getDescriptors();
if (cpdrs.size() != 0) {
for (IClassPackageDescriptor cpdr : cpdrs) {
for (String url : cpdr.getRemoteRepositories()) {
IClassPackageRepositoryRemote rr = cpdr.createRepository(url);
remoteRepositories.put(rr.getRepositoryURL().toExternalForm(), rr);
}
}
}
if (!this.resolveRemotely(ctx, remoteRepositories)) {
log.warn("Could not resolve: " + ctx.getCurrentDependency());
ctx.setUnavailable();
}
}
return ctx;
}
protected boolean resolveRemotely (ClassPackageResolverContext ctx, Map<String, IClassPackageRepositoryRemote> remoteRepositories) {
IClassPackageDependencyReference reference = ctx.getCurrentDependency();
List<IClassPackageRepositoryRemoteAttempt> attempts = this.repository.getLastRemoteAttempts(reference);
if (attempts.size() > 0) {
Set<String> urls = new HashSet<String>(remoteRepositories.keySet());
for (String url : urls) {
for (IClassPackageRepositoryRemoteAttempt attempt : attempts) {
if (attempt.getId().equals(url)) {
if (System.currentTimeMillis() - attempt.getStamp() < this.remoteAttemptInterval) {
remoteRepositories.remove(url);
}
}
}
}
}
List<IClassPackageRepositoryRemote> attempted = new ArrayList<IClassPackageRepositoryRemote>();
for (IClassPackageRepositoryRemote remote : remoteRepositories.values()) {
log.warn("Attempting to locate " + reference + " @ " + remote.getRepositoryURL());
IClassPackageDescriptor descriptor = this.getImportableRepository().getCachedRemotedDescriptor(this, remote, reference);
if (descriptor == null && remote.contains(this, reference)) {
descriptor = remote.getDescriptor(this, reference);
this.getImportableRepository().cacheRemoteDescriptor(remote, descriptor);
} else {
log.warn("Locally cached copy found of " + reference);
}
if (descriptor != null) {
if (descriptor.getRelocation() != null) {
getImportableRepository().importPackage(remote, this, descriptor);
ClassPackageDependencyReferenceImpl dep = new ClassPackageDependencyReferenceImpl(
ClassPackageReferenceImpl.getRelocation(ctx.getCurrentDependency(), descriptor.getRelocation())
);
return this.resolveRemotely(ctx.popNode().pushNode(dep, null), remoteRepositories);
}
if (descriptor.isDescriptorOnly()) {
ctx.setAvailable(this, repository, null, descriptor);
} else {
ctx.setDownloadable(this, this.repository, remote);
}
this.resolveDependencies(ctx, descriptor);
return true;
} else {
attempted.add(remote);
this.repository.cacheRemoteAttempt(attempted, reference);
}
}
return false;
}
protected boolean resolveInternally (ClassPackageResolverContext ctx) {
IClassPackageRepositoryLocal found = this.repository;
IClassPackageDescriptor descriptor = found.getDescriptor(this, ctx.getCurrentDependency());
if (descriptor == null) {
for (IClassPackageRepositoryLocal local : this.locals) {
found = local;
descriptor = local.getDescriptor(this, ctx.getCurrentDependency());
if (descriptor != null) break;
}
}
if (descriptor == null) return false;
if (descriptor.getRelocation() != null) {
ClassPackageReferenceImpl ref = ClassPackageReferenceImpl.getRelocation(ctx.getCurrentDependency(), descriptor.getRelocation());
ClassPackageDependencyReferenceImpl dep = new ClassPackageDependencyReferenceImpl(ref);
this.resolve(ctx.popNode().pushNode(dep, null));
return ctx.getResolved(ref).getType() != Type.Unavailable;
}
ClassSource src = found.getMainClassSource(this, ctx.getCurrentDependency());
if (src == null) {
// This probably means the descriptor was downloaded, but the source was not
if (descriptor.isDescriptorOnly()) {
ctx.setAvailable(this, found, null, descriptor);
return true;
}
return false;
}
ctx.setAvailable(this, found, src, descriptor);
if (ctx.isResolveDependencies()) {
printResolutionTree(ctx, "[STA] - Dependency Resolve");
this.resolveDependencies(ctx, descriptor);
printResolutionTree(ctx, "[END] - Dependency Resolve");
}
return true;
}
protected void resolveDependencies (ClassPackageResolverContext ctx, IClassPackageDescriptor descriptor) {
if (descriptor != null) {
for (IClassPackageDependencyReference reference : descriptor.getDependencies(Scope.Runtime)) {
if (reference.getVersion() == null) {
log.warn("Could not determine version for: " + reference);
continue;
}
if (ctx.isExcluded(reference) || ctx.isInStack(reference)) continue;
try {
printResolutionTree(ctx, "[STA] : " + reference);
this.resolve( ctx.pushNode(reference, descriptor) );
} catch (Exception e) {
log.warn("Failed dependency resolution for " + reference + ": " + e.getMessage(), e);
ThrowableManagerRegistry.caught(e);
} finally {
ctx.popNode();
printResolutionTree(ctx, "[END] : " + reference);
}
}
}
}
protected void printResolutionTree (ClassPackageResolverContext ctx, String msg) {
log.debug(StringUtil.repeat("->", ctx.getStackSize() - 1) + "[" + ctx.getDescriptor() + "]: " + msg);
}
}