package adept.lockfile;
import adept.artifact.AdeptCacheException;
import adept.artifact.ArtifactCache;
import adept.artifact.ArtifactDownloadResult;
import adept.artifact.ArtifactDownloader;
import adept.artifact.models.ArtifactAttribute;
import adept.artifact.models.ArtifactHash;
import adept.artifact.models.ArtifactLocation;
import adept.logging.JavaLogger;
import adept.progress.ProgressMonitor;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import java.io.*;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.*;
public class Lockfile {
final Set<LockfileRequirement> requirements;
final Set<LockfileContext> context;
final Set<LockfileArtifact> artifacts;
protected File getTmpDir() {
String tmpDir = System.getProperty("java.io.tmpdir");
if (tmpDir == null) {
throw new RuntimeException("Could not find a tmp directory because java.io.tmpdir is not set");
} else {
return new File(tmpDir);
}
}
/**
* Lockfiles gets created by factory read methods or by LockfileManager (in adept-core), only package visibility
*/
public Lockfile(Set<LockfileRequirement> requirements, Set<LockfileContext> context, Set<LockfileArtifact> artifacts) {
// we are in control of the Sets (only we can instantiate) here so even if
// they are mutable it is OK (yeah! :)
this.requirements = requirements;
this.context = context;
this.artifacts = artifacts;
}
// Getters: TODO: make copies of requirements to ensure immutability? seems a
// bit too strict though
public Set<LockfileRequirement> getRequirements() {
return requirements;
}
public Set<LockfileArtifact> getArtifacts() {
return artifacts;
}
public Set<LockfileContext> getContext() {
return context;
}
public static Constraint parseConstraint(JsonParser parser) throws IOException {
String name = null;
Set<String> values = null;
while (parser.nextToken() != JsonToken.END_OBJECT) {
String fieldName = parser.getCurrentName();
// Get value or array start token
parser.nextToken();
if (fieldName.equals("name")) {
name = parser.getValueAsString();
} else if (fieldName.equals("values")) {
values = new HashSet<String>();
assert (parser.getCurrentToken() == JsonToken.START_ARRAY);
while (parser.nextToken() != JsonToken.END_ARRAY) {
values.add(parser.getValueAsString());
}
}
}
return new Constraint(name, values);
}
public static LockfileRequirement parseRequirement(JsonParser parser) throws IOException {
assert (parser.getCurrentToken() == JsonToken.START_OBJECT);
String id = null;
Set<Constraint> constraints = null;
Set<Id> exclusions = null;
while (parser.nextToken() != JsonToken.END_OBJECT) {
assert (parser.getCurrentToken() == JsonToken.FIELD_NAME);
String fieldName = parser.getCurrentName();
// Get value or array start token
parser.nextToken();
if (fieldName.equals("id")) {
id = parser.getValueAsString();
} else if (fieldName.equals("constraints")) {
constraints = new HashSet<Constraint>();
assert (parser.getCurrentToken() == JsonToken.START_ARRAY);
while (parser.nextToken() != JsonToken.END_ARRAY) {
constraints.add(parseConstraint(parser));
}
} else if (fieldName.equals("exclusions")) {
exclusions = new HashSet<Id>();
assert (parser.getCurrentToken() == JsonToken.START_ARRAY);
while (parser.nextToken() != JsonToken.END_ARRAY) {
exclusions.add(new Id(parser.getValueAsString()));
}
}
}
return new LockfileRequirement(new Id(id), constraints, exclusions);
}
private static LockfileContext parseContext(JsonParser parser) throws IOException {
String info = null;
Id id = null;
RepositoryName repository = null;
Set<RepositoryLocation> locations = null;
Commit commit = null;
VariantHash hash = null;
assert (parser.getCurrentToken() == JsonToken.START_OBJECT);
while (parser.nextToken() != JsonToken.END_OBJECT) {
assert (parser.getCurrentToken() == JsonToken.FIELD_NAME);
String fieldName = parser.getCurrentName();
// Get value or array start token
parser.nextToken();
if (fieldName.equals("info")) {
info = parser.getValueAsString();
} else if (fieldName.equals("id")) {
id = new Id(parser.getValueAsString());
} else if (fieldName.equals("repository")) {
repository = new RepositoryName(parser.getValueAsString());
} else if (fieldName.equals("locations")) {
locations = new HashSet<RepositoryLocation>();
assert (parser.getCurrentToken() == JsonToken.START_ARRAY);
while (parser.nextToken() != JsonToken.END_ARRAY) {
locations.add(new RepositoryLocation(parser.getValueAsString()));
}
} else if (fieldName.equals("commit")) {
commit = new Commit(parser.getValueAsString());
} else if (fieldName.equals("hash")) {
hash = new VariantHash(parser.getValueAsString());
}
}
return new LockfileContext(info, id, repository, locations, commit,
hash);
}
private static ArtifactAttribute parseAttribute(JsonParser parser) throws IOException {
String name = null;
Set<String> values = null;
assert (parser.getCurrentToken() == JsonToken.START_OBJECT);
while (parser.nextToken() != JsonToken.END_OBJECT) {
assert (parser.getCurrentToken() == JsonToken.FIELD_NAME);
String fieldName = parser.getCurrentName();
// Get value or array start token
parser.nextToken();
if (fieldName.equals("name")) {
name = parser.getValueAsString();
} else if (fieldName.equals("values")) {
values = new HashSet<String>();
assert (parser.getCurrentToken() == JsonToken.START_ARRAY);
while (parser.nextToken() != JsonToken.END_ARRAY) {
values.add(parser.getValueAsString());
}
}
}
return new ArtifactAttribute(name, values);
}
private static LockfileArtifact parseArtifact(JsonParser parser) throws IOException {
ArtifactHash hash = null;
Integer size = null;
Set<ArtifactLocation> locations = null;
Set<ArtifactAttribute> attributes = null;
String filename = null;
assert (parser.getCurrentToken() == JsonToken.START_OBJECT);
while (parser.nextToken() != JsonToken.END_OBJECT) {
assert (parser.getCurrentToken() == JsonToken.FIELD_NAME);
String fieldName = parser.getCurrentName();
// Get value or array start token
parser.nextToken();
if (fieldName.equals("hash")) {
hash = new ArtifactHash(parser.getValueAsString());
} else if (fieldName.equals("size")) {
size = parser.getValueAsInt();
} else if (fieldName.equals("locations")) {
locations = new HashSet<ArtifactLocation>();
assert (parser.getCurrentToken() == JsonToken.START_ARRAY);
while (parser.nextToken() != JsonToken.END_ARRAY) {
locations.add(new ArtifactLocation(parser.getValueAsString()));
}
} else if (fieldName.equals("attributes")) {
attributes = new HashSet<ArtifactAttribute>();
assert (parser.getCurrentToken() == JsonToken.START_ARRAY);
while (parser.nextToken() != JsonToken.END_ARRAY) {
attributes.add(parseAttribute(parser));
}
} else if (fieldName.equals("filename")) {
filename = parser.getValueAsString();
break;
}
}
return new LockfileArtifact(hash, size, locations, attributes, filename);
}
public static Lockfile read(Reader data) throws LockfileParseException, IOException {
Set<LockfileRequirement> requirements = null;
Set<LockfileContext> contexts = null;
Set<LockfileArtifact> artifacts = null;
JsonParser parser = new JsonFactory().createParser(data);
try {
// Get START_OBJECT
parser.nextToken();
// Read field name or END_OBJECT
while (parser.nextToken() != JsonToken.END_OBJECT) {
assert (parser.getCurrentToken() == JsonToken.FIELD_NAME);
String fieldName = parser.getCurrentName();
// Read value, or START_OBJECT/START_ARRAY
parser.nextToken();
if (fieldName.equals("requirements")) {
requirements = new HashSet<LockfileRequirement>();
while (parser.nextToken() != JsonToken.END_ARRAY) {
requirements.add(parseRequirement(parser));
}
} else if (fieldName.equals("context")) {
contexts = new HashSet<LockfileContext>();
while (parser.nextToken() != JsonToken.END_ARRAY) {
contexts.add(parseContext(parser));
}
} else if (fieldName.equals("artifacts")) {
artifacts = new HashSet<LockfileArtifact>();
while (parser.nextToken() != JsonToken.END_ARRAY) {
artifacts.add(parseArtifact(parser));
}
}
}
}
finally {
parser.close();
}
return new Lockfile(requirements, contexts, artifacts);
}
public static Lockfile read(File file) throws LockfileParseException, IOException {
FileReader reader = null;
try {
if (!file.isFile()) {
return new Lockfile(new HashSet<LockfileRequirement>(), new HashSet<LockfileContext>(),
new HashSet<LockfileArtifact>());
} else {
reader = new FileReader(file);
return read(reader);
}
} finally {
if (reader != null)
reader.close();
}
}
protected int THREAD_POOL_SIZE = 30;
public Set<ArtifactDownloadResult> download(File baseDir, Long timeout, TimeUnit timeoutUnit, int maxRetries,
JavaLogger logger, ProgressMonitor progress)
throws InterruptedException, ExecutionException,
AdeptCacheException, IOException {
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
Set<ArtifactDownloadResult> results = new HashSet<ArtifactDownloadResult>(this.artifacts.size());
Set<LockfileArtifact> nonLocalArtifacts = new HashSet<LockfileArtifact>();
for (LockfileArtifact artifact : this.artifacts) {
File currentCachedFile = ArtifactCache.getOrCreateExistingCacheFile(baseDir, artifact.hash, artifact.filename,
true);
if (currentCachedFile == null || !currentCachedFile.isFile()) {
nonLocalArtifacts.add(artifact);
} else {
ArtifactDownloadResult result = new ArtifactDownloadResult(artifact.getArtifact(), currentCachedFile.getName());
result.setCachedFile(currentCachedFile);
results.add(result);
}
}
Set<Future<ArtifactDownloadResult>> futures = new HashSet<Future<ArtifactDownloadResult>>(nonLocalArtifacts.size());
int allSizes = 0;
for (LockfileArtifact lockfileArtifact : nonLocalArtifacts) {
allSizes += lockfileArtifact.size / 1024;
}
boolean displayProgress = !nonLocalArtifacts.isEmpty();
if (displayProgress)
progress.beginTask("Downloading (kB)", allSizes);
for (LockfileArtifact lockfileArtifact : nonLocalArtifacts) {
File tmpFile = File.createTempFile("adept-", lockfileArtifact.filename, getTmpDir());
//Initiate downloads:
futures.add(executorService.submit(new ArtifactDownloader(baseDir, lockfileArtifact.getArtifact(),
lockfileArtifact.filename, tmpFile, maxRetries, logger, progress)));
}
executorService.shutdown();
executorService.awaitTermination(timeout, timeoutUnit);
for (Future<ArtifactDownloadResult> future : futures) {
ArtifactDownloadResult result = future.get();
if (result.isSuccess()) {
results.add(result);
} else if (result.isFailed()) {
final String causeString;
if (result.exception.getCause() == null)
causeString = "";
else
causeString = " " + result.exception.getCause() + ".";
String locationsString = "";
for (ArtifactLocation location : result.artifact.locations) {
locationsString += location.value + ",";
}
locationsString = locationsString.substring(0, locationsString.length() - 1); // cut last ',' off
logger.error("Failed to get artifact with filename: '" + result.filename + "' from: " + locationsString + "."
+ causeString + " Hash: " + result.artifact.hash.value);
results.add(result);
} else {
assert (false); // Illegal state: got a download result that is neither failed nor successful!?
}
}
if (displayProgress)
progress.endTask();
return results;
}
}