Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Choose URL matching the hash of the local artifact #59

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
110 changes: 64 additions & 46 deletions src/main/java/com/fzakaria/mvn2nix/cmd/Maven2nix.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import com.fzakaria.mvn2nix.model.MavenNixInformation;
import com.fzakaria.mvn2nix.model.URLAdapter;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import com.google.common.hash.HashingInputStream;
import com.google.common.io.ByteStreams;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import org.slf4j.Logger;
Expand All @@ -25,8 +26,10 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -63,43 +66,70 @@ public class Maven2nix implements Callable<Integer> {
defaultValue = "${java.home}")
private File javaHome;

private ArtifactResolver resolver;

private ArtifactAnalysis analysis;

public Maven2nix() {
this.resolver = Maven2nix::sha256OfUrl;
this.analysis = this::collectArtifactsFromTempLocalRepository;
}

public Maven2nix(ArtifactResolver resolver, ArtifactAnalysis analysis) {
this.resolver = resolver;
this.analysis = analysis;
}

@Override
public Integer call() throws Exception {
public Integer call() {
LOGGER.info("Reading {}", file);
spec.commandLine().getOut().println(toPrettyJson(mavenNixInformation(resolver, analysis, repositories)));
return 0;
}

final Maven maven = Maven.withTemporaryLocalRepository();
maven.executeGoals(file, javaHome, goals);

Collection<Artifact> artifacts = maven.collectAllArtifactsInLocalRepository();
static MavenNixInformation mavenNixInformation(
ArtifactResolver resolver,
ArtifactAnalysis analysis,
String[] repositories
) {
Collection<Artifact> artifacts = analysis.analyze();
Map<String, MavenArtifact> dependencies = artifacts.parallelStream()
.collect(Collectors.toMap(
Artifact::getCanonicalName,
artifact -> {
for (String repository : repositories) {
URL url = getRepositoryArtifactUrl(artifact, repository);
if (!doesUrlExist(url)) {
LOGGER.info("URL does not exist: {}", url);
continue;
}
Artifact::getCanonicalName,
artifact -> new MavenArtifact(artifactUrl(resolver, repositories, artifact), artifact.getLayout(), artifact.getSha256())));
return new MavenNixInformation(dependencies);
}

File localArtifact = maven.findArtifactInLocalRepository(artifact)
.orElseThrow(() -> new IllegalStateException("Should never happen"));
private static URL artifactUrl(ArtifactResolver resolver, String[] repositories, Artifact artifact) {
return Arrays.stream(repositories)
.map(r -> getRepositoryArtifactUrl(artifact, r))
.filter(u -> resolver.sha256(u).map(artifact.getSha256()::equals).orElse(false))
.findFirst()
.orElseThrow(() -> new RuntimeException(String.format("Could not find artifact %s in any repository", artifact)));
}

String sha256 = calculateSha256OfFile(localArtifact);
return new MavenArtifact(url, artifact.getLayout(), sha256);
}
throw new RuntimeException(String.format("Could not find artifact %s in any repository", artifact));
}
));
@FunctionalInterface
interface ArtifactAnalysis {
/**
* @return The artifacts needed to run the goals on the given pom.
*/
Collection<Artifact> analyze();
}

@FunctionalInterface
public interface ArtifactResolver {
/**
* @return The sha256 of the file referred to by the given URL or
* {@link Optional#empty()} if the URL does not exist.
*/
Optional<String> sha256(URL artifact);
}

final MavenNixInformation information = new MavenNixInformation(dependencies);
spec.commandLine().getOut().println(toPrettyJson(information));

return 0;
private Collection<Artifact> collectArtifactsFromTempLocalRepository() {
final Maven maven = Maven.withTemporaryLocalRepository();
maven.executeGoals(file, javaHome, goals);
return maven.collectAllArtifactsInLocalRepository();
}

/**
Expand Down Expand Up @@ -130,40 +160,28 @@ public static URL getRepositoryArtifactUrl(Artifact artifact, String repository)
}
}

public static String calculateSha256OfFile(File file) {
try {
return Files.asByteSource(file).hash(Hashing.sha256()).toString();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

/**
* Check whether a given URL
*
* @param url The URL for the pom file.
* @return
*/
public static boolean doesUrlExist(URL url) {
public static Optional<String> sha256OfUrl(URL url) {
try {
URLConnection urlConnection = url.openConnection();
if (!(urlConnection instanceof HttpURLConnection)) {
return false;
throw new RuntimeException("The url is not of type http provided.");
}

HttpURLConnection connection = (HttpURLConnection) urlConnection;
connection.setRequestMethod("HEAD");
connection.setRequestMethod("GET");
connection.setInstanceFollowRedirects(true);
connection.connect();

int code = connection.getResponseCode();
if (code == 200) {
return true;
if (code >= 400) {
throw new RuntimeException("Fetching the url failed with status code: " + code);
}
var inputStream = new HashingInputStream(Hashing.sha256(), connection.getInputStream());
ByteStreams.exhaust(inputStream);
return Optional.of(inputStream.hash().toString());
} catch (IOException e) {
throw new UncheckedIOException(e);
return Optional.empty();
}
return false;
}

}
35 changes: 17 additions & 18 deletions src/main/java/com/fzakaria/mvn2nix/maven/Artifact.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,23 @@ public class Artifact {
private final String classifier;
private final String extension;

public Artifact(String group, String name, String version, String classifier, String extension) {
private final String sha256;

public Artifact(String group, String name, String version, String classifier, String extension, String sha256) {
this.group = group;
this.name = name;
this.version = version;
this.classifier = classifier;
this.extension = extension;
}

public String getGroup() {
return group;
this.sha256 = sha256;
}

public String getName() {
return name;
}

public String getVersion() {
return version;
}

public String getClassifier() {
return classifier;
}

public String getExtension() {
return extension;
public String getSha256() {
return sha256;
}

@Override
Expand All @@ -51,7 +42,8 @@ public boolean equals(Object o) {
Objects.equals(name, artifact.name) &&
Objects.equals(version, artifact.version) &&
Objects.equals(classifier, artifact.classifier) &&
Objects.equals(extension, artifact.extension);
Objects.equals(extension, artifact.extension) &&
Objects.equals(sha256, artifact.sha256);
}

/**
Expand Down Expand Up @@ -87,7 +79,7 @@ public String getCanonicalName() {

@Override
public int hashCode() {
return Objects.hash(group, name, version, classifier, extension);
return Objects.hash(group, name, version, classifier, extension, sha256);
}

@Override
Expand Down Expand Up @@ -139,6 +131,8 @@ public static class Builder {
private String classifier;
private String extension;

private String sha256;

public Builder setGroup(String group) {
this.group = group;
return this;
Expand All @@ -164,8 +158,13 @@ public Builder setExtension(String extension) {
return this;
}

public Builder setSha256(String sha256) {
this.sha256 = sha256;
return this;
}

public Artifact build() {
return new Artifact(group, name, version, classifier, extension);
return new Artifact(group, name, version, classifier, extension, sha256);
}
}
}
43 changes: 20 additions & 23 deletions src/main/java/com/fzakaria/mvn2nix/maven/Maven.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fzakaria.mvn2nix.util.Resources;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.hash.Hashing;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.io.IoBuilder;
import org.apache.maven.shared.invoker.DefaultInvocationRequest;
Expand All @@ -21,7 +22,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

Expand Down Expand Up @@ -70,21 +70,7 @@ public static Maven withTemporaryLocalRepository() {
}
}

/**
* Return the local File for the provided artifact if it exists.
* The ephemeral local repository is checked only.
* @param artifact
* @return
*/
public Optional<File> findArtifactInLocalRepository(Artifact artifact) {
File file = localRepository.toPath().resolve(artifact.getLayout()).toFile();
if (!file.exists()) {
return Optional.empty();
}
return Optional.of(file);
}

public void executeGoals(File pom, File javaHome, String... goals) throws MavenInvocationException {
public void executeGoals(File pom, File javaHome, String... goals) {
InvocationRequest request = new DefaultInvocationRequest();
request.setGoals(Lists.newArrayList(goals));
request.setBatchMode(true);
Expand All @@ -96,13 +82,16 @@ public void executeGoals(File pom, File javaHome, String... goals) throws MavenI
* This will cut down drastically on the network calls to re-hydrate this temporary local repo.
*/
request.setGlobalSettingsFile(Resources.export("settings.xml"));

InvocationResult result = this.invoker.execute(request);
if (result.getExitCode() != 0) {
throw new MavenInvocationException(
String.format("Failed to execute goals [%s]. Exit code: %s", Arrays.toString(goals), result.getExitCode()),
result.getExecutionException()
);
try {
InvocationResult result = this.invoker.execute(request);
if (result.getExitCode() != 0) {
throw new MavenInvocationException(
String.format("Failed to execute goals [%s]. Exit code: %s", Arrays.toString(goals), result.getExitCode()),
result.getExecutionException()
);
}
} catch (MavenInvocationException e) {
throw new RuntimeException(e);
}
}

Expand Down Expand Up @@ -154,6 +143,7 @@ public Collection<Artifact> collectAllArtifactsInLocalRepository() {
.setClassifier(classifier)
.setVersion(version)
.setExtension(extension)
.setSha256(calculateSha256OfFile(file.toFile()))
.build();
})
.filter(Objects::nonNull)
Expand All @@ -163,4 +153,11 @@ public Collection<Artifact> collectAllArtifactsInLocalRepository() {
}
}

private static String calculateSha256OfFile(File file) {
try {
return com.google.common.io.Files.asByteSource(file).hash(Hashing.sha256()).toString();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
import com.google.common.base.MoreObjects;

import javax.annotation.concurrent.Immutable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;

@Immutable
public class MavenNixInformation {

/**
* Maps canonical names to the artifact data.
*/
private final Map<String, MavenArtifact> dependencies;

/**
Expand All @@ -21,6 +23,10 @@ public MavenNixInformation(Map<String, MavenArtifact> dependencies) {
this.dependencies = new TreeMap<>(dependencies);
}

public MavenArtifact byCanonicalName(String canonicalName) {
return dependencies.get(canonicalName);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down
Loading
Loading