/** * Copyright 2015-2017 Red Hat, Inc, and individual contributors. * * 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.wildfly.swarm.bootstrap.modules; import org.jboss.modules.Module; import org.jboss.modules.maven.ArtifactCoordinates; import org.jboss.modules.maven.MavenArtifactUtil; import org.jboss.modules.maven.MavenResolver; import javax.xml.xpath.XPathExpressionException; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.stream.Collectors; /** * This resolver try to find the requested artifact in the local gradle cache. If the artifact is missing, it try * to download it from a remote repository. Default is the https://repo1.maven.org/maven2/ repository. With the * system property 'remote.maven.repo' it's possible to specify additional repositories. * * @author Michael Fraefel */ public class GradleResolver implements MavenResolver { private final String gradleCachePath; private final List<String> remoteRepositories = new LinkedList<>(); public GradleResolver(String gradleCachePath) { this.gradleCachePath = gradleCachePath; String remoteRepository = System.getProperty("remote.maven.repo"); if (remoteRepository != null) { for (String repo : remoteRepository.split(",")) { if (!repo.endsWith("/")) { this.remoteRepositories.add(repo + "/"); } else { this.remoteRepositories.add(repo); } } } this.remoteRepositories.add("https://repo1.maven.org/maven2/"); } @Override public File resolveArtifact(ArtifactCoordinates artifactCoordinates, String packaging) throws IOException { //Search the matching artifact in a gradle cache. String filter = toGradleArtifactFileName(artifactCoordinates, packaging); Path artifactDirectory = Paths.get(gradleCachePath, artifactCoordinates.getGroupId(), artifactCoordinates.getArtifactId(), artifactCoordinates.getVersion()); if (Files.exists(artifactDirectory)) { File latestArtifactFile = null; for (Path hashDir : Files.list(artifactDirectory).collect(Collectors.toList())) { for (Path artifact : Files.list(hashDir).collect(Collectors.toList())) { if (artifact.endsWith(filter)) { File artifactFile = artifact.toFile(); if (latestArtifactFile == null || latestArtifactFile.lastModified() < artifactFile.lastModified()) { //take always the latest version of the artifact latestArtifactFile = artifactFile; } } } } if (latestArtifactFile != null) { return latestArtifactFile; } } //Artifact not found in the locale gradle cache. Try to resolve it from the remote respository return downloadFromRemoteRepository(artifactCoordinates, packaging, artifactDirectory); } /** * Download artifact from remote repository. * * @param artifactCoordinates * @param packaging * @param artifactDirectory * @return */ File downloadFromRemoteRepository(ArtifactCoordinates artifactCoordinates, String packaging, Path artifactDirectory) { String artifactRelativeHttpPath = artifactCoordinates.relativeArtifactPath('/'); String artifactRelativeMetadataHttpPath = artifactCoordinates.relativeMetadataPath('/'); for (String remoteRepos : remoteRepositories) { String artifactAbsoluteHttpPath = remoteRepos + artifactRelativeHttpPath + "."; File targetArtifactPomDirectory = artifactDirectory.resolve(computeGradleUUID(artifactCoordinates + ":pom")).toFile(); File targetArtifactDirectory = artifactDirectory.resolve(computeGradleUUID(artifactCoordinates + ":" + packaging)).toFile(); File artifactFile = doDownload(remoteRepos, artifactAbsoluteHttpPath, artifactRelativeHttpPath, artifactCoordinates, packaging, targetArtifactPomDirectory, targetArtifactDirectory); if (artifactFile != null) { return artifactFile; // Success } //Try to doDownload the snapshot version if (artifactCoordinates.isSnapshot()) { String remoteMetadataPath = remoteRepos + artifactRelativeMetadataHttpPath; try { String timestamp = MavenArtifactUtil.downloadTimestampVersion(artifactCoordinates + ":" + packaging, remoteMetadataPath); String timestampedArtifactRelativePath = artifactCoordinates.relativeArtifactPath('/', timestamp); String artifactTimestampedAbsoluteHttpPath = remoteRepos + timestampedArtifactRelativePath + "."; File targetTimestampedArtifactPomDirectory = artifactDirectory.resolve(computeGradleUUID(artifactCoordinates + ":" + timestamp + ":pom")).toFile(); File targetTimestampedArtifactDirectory = artifactDirectory.resolve(computeGradleUUID(artifactCoordinates + ":" + packaging)).toFile(); File snapshotArtifactFile = doDownload(remoteRepos, artifactTimestampedAbsoluteHttpPath, timestampedArtifactRelativePath, artifactCoordinates, packaging, targetTimestampedArtifactPomDirectory, targetTimestampedArtifactDirectory); if (snapshotArtifactFile != null) { return snapshotArtifactFile; //Success } } catch (XPathExpressionException | IOException ex) { Module.getModuleLogger().trace(ex, "Could not doDownload '%s' from '%s' repository", artifactRelativeHttpPath, remoteRepos); // try next one } } } return null; } /** * Download the POM and the artifact file and return it. * * @param remoteRepos * @param artifactAbsoluteHttpPath * @param artifactRelativeHttpPath * @param artifactCoordinates * @param packaging * @param targetArtifactPomDirectory * @param targetArtifactDirectory * @return */ File doDownload(String remoteRepos, String artifactAbsoluteHttpPath, String artifactRelativeHttpPath, ArtifactCoordinates artifactCoordinates, String packaging, File targetArtifactPomDirectory, File targetArtifactDirectory) { //Download POM File targetArtifactPomFile = new File(targetArtifactPomDirectory, toGradleArtifactFileName(artifactCoordinates, "pom")); try { MavenArtifactUtil.downloadFile(artifactCoordinates + ":pom", artifactAbsoluteHttpPath + "pom", targetArtifactPomFile); } catch (IOException e) { Module.getModuleLogger().trace(e, "Could not doDownload '%s' from '%s' repository", artifactRelativeHttpPath, remoteRepos); // try next one } //Download Artifact File targetArtifactFile = new File(targetArtifactDirectory, toGradleArtifactFileName(artifactCoordinates, packaging)); try { MavenArtifactUtil.downloadFile(artifactCoordinates + ":" + packaging, artifactAbsoluteHttpPath + packaging, targetArtifactFile); if (targetArtifactFile.exists()) { return targetArtifactFile; } } catch (IOException e) { Module.getModuleLogger().trace(e, "Could not doDownload '%s' from '%s' repository", artifactRelativeHttpPath, remoteRepos); // try next one } return null; } /** * Build file name for artifact. * * @param artifactCoordinates * @param packaging * @return */ String toGradleArtifactFileName(ArtifactCoordinates artifactCoordinates, String packaging) { StringBuilder sbFileFilter = new StringBuilder(); sbFileFilter .append(artifactCoordinates.getArtifactId()) .append("-") .append(artifactCoordinates.getVersion()); if (artifactCoordinates.getClassifier() != null && artifactCoordinates.getClassifier().length() > 0) { sbFileFilter .append("-") .append(artifactCoordinates.getClassifier()); } sbFileFilter .append(".") .append(packaging); return sbFileFilter.toString(); } /** * Compute gradle uuid for artifacts. * * @param content * @return */ String computeGradleUUID(String content) { try { MessageDigest md = MessageDigest.getInstance(MD5_ALGORITHM); md.reset(); byte[] bytes = content.trim().toLowerCase(Locale.US).getBytes("UTF-8"); md.update(bytes, 0, bytes.length); return byteArrayToHexString(md.digest()); } catch (NoSuchAlgorithmException e) { // Impossible throw new IllegalArgumentException("unknown algorithm " + MD5_ALGORITHM, e); } catch (UnsupportedEncodingException e) { // Impossible except with IBM :) throw new IllegalArgumentException("unknown charset UTF-8", e); } } String byteArrayToHexString(byte[] in) { byte ch = 0x00; if (in == null || in.length <= 0) { return null; } StringBuffer out = new StringBuffer(in.length * 2); //CheckStyle:MagicNumber OFF for (byte b : in) { ch = (byte) (b & 0xF0); // Strip off high nibble ch = (byte) (ch >>> 4); // shift the bits down ch = (byte) (ch & 0x0F); // must do this is high order bit is on! out.append(CHARS[(int) ch]); // convert the nibble to a String Character ch = (byte) (b & 0x0F); // Strip off low nibble out.append(CHARS[(int) ch]); // convert the nibble to a String Character } //CheckStyle:MagicNumber ON return out.toString(); } // algorithm for gradle uuid private static final String MD5_ALGORITHM = "md5"; // byte to hex string converter private static final char[] CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; }