package aQute.maven.provider;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import org.osgi.util.function.Function;
import org.osgi.util.promise.Deferred;
import org.osgi.util.promise.Promise;
import org.osgi.util.promise.Promises;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import aQute.bnd.service.url.State;
import aQute.bnd.service.url.TaggedData;
import aQute.bnd.version.MavenVersion;
import aQute.lib.io.IO;
import aQute.lib.strings.Strings;
import aQute.maven.api.Archive;
import aQute.maven.api.IMavenRepo;
import aQute.maven.api.Program;
import aQute.maven.api.Release;
import aQute.maven.api.Revision;
import aQute.service.reporter.Reporter;
public class MavenRepository implements IMavenRepo, Closeable {
private final static Logger logger = LoggerFactory.getLogger(MavenRepository.class);
private final File base;
private final String id;
private final List<MavenBackingRepository> release = new ArrayList<>();
private final List<MavenBackingRepository> snapshot = new ArrayList<>();
private final Executor executor;
private final boolean localOnly;
private final Map<Revision,Promise<POM>> poms = new WeakHashMap<>();
public MavenRepository(File base, String id, List<MavenBackingRepository> release,
List<MavenBackingRepository> snapshot, Executor executor, Reporter reporter, Callable<Boolean> callback)
throws Exception {
this.base = base;
this.id = id;
if (release != null)
this.release.addAll(release);
if (snapshot != null)
this.snapshot.addAll(snapshot);
this.executor = executor == null ? Executors.newCachedThreadPool() : executor;
this.localOnly = this.release.isEmpty() && this.snapshot.isEmpty();
IO.mkdirs(base);
}
@Override
public List<Revision> getRevisions(Program program) throws Exception {
List<Revision> revisions = new ArrayList<>();
for (MavenBackingRepository mbr : release)
mbr.getRevisions(program, revisions);
for (MavenBackingRepository mbr : snapshot)
if (!release.contains(mbr))
mbr.getRevisions(program, revisions);
return revisions;
}
@Override
public List<Archive> getSnapshotArchives(Revision revision) throws Exception {
if (!revision.isSnapshot())
return null;
List<Archive> archives = new ArrayList<>();
for (MavenBackingRepository mbr : snapshot) {
List<Archive> snapshotArchives = mbr.getSnapshotArchives(revision);
archives.addAll(snapshotArchives);
}
return archives;
}
@Override
public Archive getResolvedArchive(Revision revision, String extension, String classifier) throws Exception {
if (revision.isSnapshot()) {
for (MavenBackingRepository mbr : snapshot) {
MavenVersion v = mbr.getVersion(revision);
if (v != null)
return revision.archive(v, extension, classifier);
}
return null;
} else {
return revision.archive(extension, classifier);
}
}
@Override
public Release release(final Revision revision, final Properties context) throws Exception {
logger.debug("Release {} to {}", revision, this);
if (revision.isSnapshot()) {
return new SnapshotReleaser(this, revision, snapshot.isEmpty() ? null : snapshot.get(0), context);
}
return new Releaser(this, revision, release.isEmpty() ? null : release.get(0), context);
}
@Override
public Promise<File> get(final Archive archive) throws Exception {
return get(archive, true);
}
private Promise<File> get(final Archive archive, final boolean thrw) throws Exception {
final File file = toLocalFile(archive);
if (file.isFile() && !archive.isSnapshot()) {
return Promises.resolved(file);
}
if (localOnly || isFresh(file)) {
return Promises.resolved(file.isFile() ? file : null);
}
final Deferred<File> deferred = new Deferred<>();
executor.execute(new Runnable() {
@Override
public void run() {
try {
File f = getFile(archive, file);
if (thrw && f == null) {
deferred.fail(new FileNotFoundException("For Maven artifact " + archive));
return;
}
deferred.resolve(f);
} catch (Throwable e) {
deferred.fail(e);
}
}
});
return deferred.getPromise();
}
private boolean isFresh(File file) {
if (!file.isFile())
return false;
long now = System.currentTimeMillis();
long diff = now - file.lastModified();
return diff < TimeUnit.DAYS.toMillis(1);
}
File getFile(Archive archive, File file) throws Exception {
State result = null;
if (archive.isSnapshot()) {
Archive resolved = resolveSnapshot(archive);
if (resolved == null) {
// Cannot resolved snapshot
if (file.isFile()) // use local copy
return file;
return null;
}
if (resolved != null) {
result = fetch(snapshot, resolved.remotePath, file);
}
}
if (result == null && release != null)
result = fetch(release, archive.remotePath, file);
if (result == null)
throw new IllegalStateException("Neither release nor remote repo set");
switch (result) {
case NOT_FOUND :
return null;
case OTHER :
throw new IOException("Could not fetch " + archive.toString());
case UNMODIFIED :
case UPDATED :
default :
return file;
}
}
private State fetch(List<MavenBackingRepository> mbrs, String remotePath, File file) throws Exception {
State error = State.NOT_FOUND;
for (MavenBackingRepository mbr : mbrs) {
TaggedData fetch = mbr.fetch(remotePath, file);
switch (fetch.getState()) {
case NOT_FOUND :
break;
case OTHER :
error = State.OTHER;
logger.error("Fetching artifact gives error {}", remotePath);
break;
case UNMODIFIED :
case UPDATED :
return fetch.getState();
}
}
return error;
}
@Override
public Archive resolveSnapshot(Archive archive) throws Exception {
if (archive.isResolved())
return archive;
for (MavenBackingRepository mbr : snapshot) {
MavenVersion version = mbr.getVersion(archive.revision);
if (version != null)
return archive.resolveSnapshot(version);
}
return null;
}
public File toLocalFile(String path) {
return IO.getFile(base, path);
}
@Override
public File toLocalFile(Archive archive) {
return toLocalFile(archive.localPath);
}
public long getLastUpdated(Revision revision) throws Exception {
if (revision.isSnapshot()) {
File metafile = toLocalFile(revision.metadata(id));
return metafile.lastModified();
} else {
File dir = toLocalFile(revision.path);
return dir.lastModified();
}
}
@Override
public Archive getArchive(String s) throws Exception {
Matcher matcher = ARCHIVE_P.matcher(Strings.trim(s));
if (!matcher.matches())
return null;
String group = Strings.trim(matcher.group("group"));
String artifact = Strings.trim(matcher.group("artifact"));
String extension = Strings.trim(matcher.group("extension"));
String classifier = Strings.trim(matcher.group("classifier"));
String version = Strings.trim(matcher.group("version"));
return Program.valueOf(group, artifact).version(version).archive(extension, classifier);
}
@Override
public void close() throws IOException {
for (MavenBackingRepository mbr : snapshot)
IO.close(mbr);
for (MavenBackingRepository mbr : release)
IO.close(mbr);
}
@Override
public URI toRemoteURI(Archive archive) throws Exception {
if (archive.revision.isSnapshot()) {
if (snapshot != null && !snapshot.isEmpty())
return snapshot.get(0).toURI(archive.remotePath);
} else {
if (release != null && !release.isEmpty())
return release.get(0).toURI(archive.remotePath);
}
return toLocalFile(archive).toURI();
}
public void store(Archive archive, InputStream in) throws IOException {
File file = IO.getFile(base, archive.localPath);
IO.copy(in, file);
}
@Override
public boolean refresh() throws IOException {
// TODO
return false;
}
@Override
public String toString() {
return "MavenRepository [base=" + base + ", id=" + id + ", release=" + release + ", snapshot=" + snapshot
+ ", localOnly=" + localOnly + "]";
}
@Override
public String getName() {
return id;
}
@Override
public POM getPom(InputStream pomFile) throws Exception {
return new POM(this, pomFile, true);
}
@Override
public POM getPom(Revision revision) throws Exception {
if (revision == null) {
return null;
}
return getPomPromise(revision).getValue();
}
private Promise<POM> getPomPromise(final Revision revision) throws Exception {
Deferred<POM> deferred;
synchronized (poms) {
Promise<POM> promise = poms.get(revision);
if (promise != null) {
return promise;
}
deferred = new Deferred<>();
poms.put(revision, deferred.getPromise());
}
Archive pomArchive = revision.getPomArchive();
deferred.resolveWith(get(pomArchive, false).map(new Function<File,POM>() {
@Override
public POM apply(File pomFile) {
if (pomFile == null) {
return null;
}
try (InputStream fin = IO.stream(pomFile)) {
return getPom(fin);
} catch (Exception e) {
logger.error("Failed to parse pom {} from file {}", revision, pomFile, e);
return null;
}
}
}));
return deferred.getPromise();
}
@Override
public List<MavenBackingRepository> getSnapshotRepositories() {
return Collections.unmodifiableList(snapshot);
}
@Override
public List<MavenBackingRepository> getReleaseRepositories() {
return Collections.unmodifiableList(release);
}
@Override
public boolean isLocalOnly() {
return localOnly;
}
@Override
public boolean exists(Archive archive) throws Exception {
File file = File.createTempFile("pom", ".xml");
try {
File result = getFile(archive.getPomArchive(), file);
return result != null;
} catch (Exception e) {
return false;
} finally {
IO.delete(file);
}
}
public void clear(Revision revision) {
synchronized (poms) {
poms.remove(revision);
}
}
}