All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.takari.maven.testing.executor.junit.MavenVersionResolver Maven / Gradle / Ivy

There is a newer version: 3.0.5
Show newest version
/**
 * Copyright (c) 2014 Takari, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package io.takari.maven.testing.executor.junit;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.PosixFilePermission;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.junit.runners.model.InitializationError;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import io.takari.maven.testing.TestProperties;

abstract class MavenVersionResolver {
  private static final XPathFactory xpathFactory = XPathFactory.newInstance();
  private static final DocumentBuilderFactory documentBuilderFactory;

  static {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    // settings.xml may have default xmlns, which may confuse xpath if not suppressed
    factory.setNamespaceAware(false);
    documentBuilderFactory = factory;
  }

  private static class Credentials {
    public final String username;
    public final String password;

    public Credentials(String username, String password) {
      this.username = username;
      this.password = password;
    }
  }

  private static class Repository {
    public final URL url;
    public final Credentials credentials;

    public Repository(URL url, Credentials credentials) {
      this.url = url;
      this.credentials = credentials;
    }
  }

  public void resolve(String[] versions) throws Exception {
    List repositories = null;
    TestProperties properties = new TestProperties();
    for (String version : versions) {
      // refuse to test with SNAPSHOT maven version when build RELEASE plugins
      if (isSnapshot(version) && !isSnapshot(properties.getPluginVersion())) {
        String msg = String.format("Cannot test %s plugin release with %s maven", properties.getPluginVersion(), version);
        error(version, new IllegalStateException(msg));
      }
      File basdir = new File("target/maven-installation").getCanonicalFile();
      File mavenHome = new File(basdir, "apache-maven-" + version).getCanonicalFile();
      if (!mavenHome.isDirectory()) {
        if (repositories == null) {
          repositories = getRepositories(properties);
        }
        Authenticator defaultAuthenticator = getDefaultAuthenticator();
        try {
          createMavenInstallation(repositories, version, properties.getLocalRepository(), basdir);
        } catch (Exception e) {
          error(version, e);
        } finally {
          Authenticator.setDefault(defaultAuthenticator);
        }
      }
      if (mavenHome.isDirectory()) {
        resolved(mavenHome, version);
      }
    }
  }

  private static Authenticator getDefaultAuthenticator() {
    // there is no API to query current default Authenticator
    // assume that integration test jvm does not have any at this point
    return null;
  }

  private boolean isSnapshot(String version) {
    return version != null && version.endsWith("-SNAPSHOT");
  }

  private void unarchive(File archive, File directory) throws IOException {
    try (TarArchiveInputStream ais = new TarArchiveInputStream(new GzipCompressorInputStream(new FileInputStream(archive)))) {
      TarArchiveEntry entry;
      while ((entry = ais.getNextTarEntry()) != null) {
        if (entry.isFile()) {
          String name = entry.getName();
          File file = new File(directory, name);
          file.getParentFile().mkdirs();
          try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
            copy(ais, os);
          }
          int mode = entry.getMode();
          if (mode != -1 && (mode & 0100) != 0) {
            try {
              Path path = file.toPath();
              Set permissions = Files.getPosixFilePermissions(path);
              permissions.add(PosixFilePermission.OWNER_EXECUTE);
              Files.setPosixFilePermissions(path, permissions);
            } catch (UnsupportedOperationException e) {
              // must be windows, ignore
            }
          }
        }
      }
    }
  }

  private List getRepositories(TestProperties properties) throws Exception {
    Map credentials = getCredentials(properties);
    List repositories = new ArrayList<>();
    for (String property : properties.getRepositories()) {
      InputSource is = new InputSource(new StringReader("" + property + ""));
      Element repository = (Element) xpathFactory.newXPath() //
          .compile("/repository") //
          .evaluate(is, XPathConstants.NODE);
      String url = getChildValue(repository, "url");
      if (url == null) {
        continue; // malformed test.properties
      }
      if (!url.endsWith("/")) {
        url = url + "/";
      }
      String id = getChildValue(repository, "id");
      repositories.add(new Repository(new URL(url), credentials.get(id)));
    }
    return repositories;
  }

  private Map getCredentials(TestProperties properties) throws IOException {
    File userSettings = properties.getUserSettings();
    if (userSettings == null) {
      return Collections.emptyMap();
    }
    Map result = new HashMap<>();
    try {
      Document document = documentBuilderFactory.newDocumentBuilder().parse(userSettings);
      NodeList servers = (NodeList) xpathFactory.newXPath() //
          .compile("//settings/servers/server") //
          .evaluate(document, XPathConstants.NODESET);
      for (int i = 0; i < servers.getLength(); i++) {
        Element server = (Element) servers.item(i);
        String id = getChildValue(server, "id");
        String username = getChildValue(server, "username");
        String password = getChildValue(server, "password");
        if (id != null && username != null) {
          result.put(id, new Credentials(username, password));
        }
      }
    } catch (XPathExpressionException | SAXException | ParserConfigurationException e) {
      // can't happen
    }
    return result;
  }

  private String getChildValue(Element server, String name) {
    NodeList children = server.getElementsByTagName(name);
    if (children.getLength() != 1) {
      return null;
    }
    String value = ((Element) children.item(0)).getTextContent();
    if (value != null) {
      value = value.trim();
    }
    return !value.isEmpty() ? value : null;
  }

  private String getXPathString(InputSource is, String path) throws Exception {
    String value = xpathFactory.newXPath().compile(path).evaluate(is);
    if (value == null) {
      return null;
    }
    value = value.trim();
    return !value.isEmpty() ? value : null;
  }

  private void createMavenInstallation(List repositories, String version, File localrepo, File targetdir) throws Exception {
    String versionDir = "org/apache/maven/apache-maven/" + version + "/";
    String filename = "apache-maven-" + version + "-bin.tar.gz";
    File archive = new File(localrepo, versionDir + filename);
    if (archive.canRead()) {
      unarchive(archive, targetdir);
      return;
    }
    Exception cause = null;
    for (Repository repository : repositories) {
      setHttpCredentials(repository.credentials);
      String effectiveVersion;
      if (isSnapshot(version)) {
        try {
          effectiveVersion = getQualifiedVersion(repository.url, versionDir);
        } catch (FileNotFoundException e) {
          continue;
        } catch (IOException e) {
          cause = e;
          continue;
        }
        if (effectiveVersion == null) {
          continue;
        }
      } else {
        effectiveVersion = version;
      }
      filename = "apache-maven-" + effectiveVersion + "-bin.tar.gz";
      archive = new File(localrepo, versionDir + filename);
      if (archive.canRead()) {
        unarchive(archive, targetdir);
        return;
      }
      URL resource = new URL(repository.url, versionDir + filename);
      try (InputStream is = openStream(resource)) {
        archive.getParentFile().mkdirs();
        File tmpfile = File.createTempFile(filename, ".tmp", archive.getParentFile());
        try {
          copy(is, tmpfile);
          unarchive(tmpfile, targetdir);
          Files.move(tmpfile.toPath(), archive.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
        } finally {
          tmpfile.delete();
        }
        return;
      } catch (FileNotFoundException e) {
        // ignore the exception. this is expected to happen quite often and not a failure by iteself
      } catch (IOException e) {
        cause = e;
      }
    }
    Exception exception = new FileNotFoundException("Could not download maven version " + version + " from any configured repository");
    exception.initCause(cause);
    throw exception;
  }

  private void setHttpCredentials(final Credentials credentials) {
    Authenticator authenticator = null;
    if (credentials != null) {
      authenticator = new Authenticator() {
        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
          return new PasswordAuthentication(credentials.username, credentials.password.toCharArray());
        }
      };
    }
    Authenticator.setDefault(authenticator);
  }

  private String getQualifiedVersion(URL repository, String versionDir) throws Exception {
    URL resource = new URL(repository, versionDir + "maven-metadata.xml");
    try (InputStream is = openStream(resource)) {
      ByteArrayOutputStream buf = new ByteArrayOutputStream();
      copy(is, buf);
      InputSource xml = new InputSource(new ByteArrayInputStream(buf.toByteArray()));
      String version = getXPathString(xml, "//metadata/versioning/snapshotVersions/snapshotVersion[extension='tar.gz']/value");
      if (version == null) {
        return null;
      }
      version = version.trim();
      return version.isEmpty() ? null : version;
    }
  }

  private InputStream openStream(URL resource) throws IOException {
    URLConnection connection = resource.openConnection();
    if (connection instanceof HttpURLConnection) {
      // for some reason, nexus version 2.11.1-01 returns partial maven-metadata.xml
      // unless request User-Agent header is set to non-default value
      connection.addRequestProperty("User-Agent", "takari-plugin-testing");
      int responseCode = ((HttpURLConnection) connection).getResponseCode();
      if (responseCode < 200 || responseCode > 299) {
        String message = String.format("HTTP/%d %s", responseCode, ((HttpURLConnection) connection).getResponseMessage());
        throw responseCode == HttpURLConnection.HTTP_NOT_FOUND ? new FileNotFoundException(message) : new IOException(message);
      }
    }
    return connection.getInputStream();
  }

  private void copy(InputStream from, File to) throws IOException {
    to.getParentFile().mkdirs();
    try (OutputStream out = new FileOutputStream(to)) {
      copy(from, out);
    }
  }

  private void copy(InputStream from, OutputStream to) throws IOException {
    byte[] buf = new byte[4096];
    int len;
    while ((len = from.read(buf)) > 0) {
      to.write(buf, 0, len);
    }
  }

  protected abstract void error(String version, Exception e);

  protected abstract void resolved(File mavenHome, String version) throws InitializationError;
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy