Java tutorial
/** * 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<Repository> 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<PosixFilePermission> permissions = Files.getPosixFilePermissions(path); permissions.add(PosixFilePermission.OWNER_EXECUTE); Files.setPosixFilePermissions(path, permissions); } catch (UnsupportedOperationException e) { // must be windows, ignore } } } } } } private List<Repository> getRepositories(TestProperties properties) throws Exception { Map<String, Credentials> credentials = getCredentials(properties); List<Repository> repositories = new ArrayList<>(); for (String property : properties.getRepositories()) { InputSource is = new InputSource(new StringReader("<repository>" + property + "</repository>")); 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<String, Credentials> getCredentials(TestProperties properties) throws IOException { File userSettings = properties.getUserSettings(); if (userSettings == null) { return Collections.emptyMap(); } Map<String, Credentials> 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<Repository> 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; }