Java tutorial
/******************************************************************************* * (c) Copyright 2014 Hewlett-Packard Development Company, L.P. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Apache License v2.0 which accompany this distribution. * * The Apache License is available at * http://www.apache.org/licenses/LICENSE-2.0 * *******************************************************************************/ package io.cloudslang.dependency.impl.services; import io.cloudslang.dependency.api.services.DependencyService; import io.cloudslang.dependency.api.services.MavenConfig; import org.apache.log4j.Appender; import org.apache.log4j.FileAppender; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import javax.annotation.PostConstruct; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.*; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.*; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import static io.cloudslang.dependency.api.services.MavenConfig.SEPARATOR; /** * @author Alexander Eskin */ @Component @SuppressWarnings("unused") public class DependencyServiceImpl implements DependencyService { private static final Logger logger = Logger.getLogger(DependencyServiceImpl.class); private static final String MAVEN_LAUNCHER_CLASS_NAME = "org.codehaus.plexus.classworlds.launcher.Launcher"; private static final String MAVEN_LANUCHER_METHOD_NAME = "mainWithExitCode"; public static final String PATH_FILE_EXTENSION = "path"; public static final String GAV_DELIMITER = ":"; protected static final String PATH_FILE_DELIMITER = ";"; private static final int MINIMAL_GAV_PARTS = 3; private static final int MAXIMAL_GAV_PARTS = 5; private static final String GAV_SEPARATOR = "_"; private Method launcherMethod; @Value("#{systemProperties['" + MavenConfig.MAVEN_HOME + "']}") private String mavenHome; private ClassLoader mavenClassLoader; private Method MAVEN_EXECUTE_METHOD; private String mavenLogFolder; @Autowired private MavenConfig mavenConfig; private final Lock lock = new ReentrantLock(); @PostConstruct private void initMaven() throws ClassNotFoundException, NoSuchMethodException, MalformedURLException { ClassLoader parentClassLoader = DependencyServiceImpl.class.getClassLoader(); while (parentClassLoader.getParent() != null) { parentClassLoader = parentClassLoader.getParent(); } if (isMavenConfigured()) { File libDir = new File(mavenHome, "boot"); if (libDir.exists()) { URL[] mavenJarUrls = getUrls(libDir); mavenClassLoader = new URLClassLoader(mavenJarUrls, parentClassLoader); MAVEN_EXECUTE_METHOD = Class.forName(MAVEN_LAUNCHER_CLASS_NAME, true, mavenClassLoader) .getMethod(MAVEN_LANUCHER_METHOD_NAME, String[].class); initMavenLogs(); } } } private void initMavenLogs() { File mavenLogFolderFile = new File(new File(calculateLogFolderPath()), MavenConfig.MAVEN_FOLDER); mavenLogFolderFile.mkdirs(); this.mavenLogFolder = mavenLogFolderFile.getAbsolutePath(); } private String calculateLogFolderPath() { Enumeration e = Logger.getRootLogger().getAllAppenders(); while (e.hasMoreElements()) { Appender app = (Appender) e.nextElement(); if (app instanceof FileAppender) { String logFile = ((FileAppender) app).getFile(); return new File(logFile).getParentFile().getAbsolutePath(); } } return new File(System.getProperty(MavenConfig.APP_HOME), MavenConfig.LOGS_FOLDER_NAME).getAbsolutePath(); } protected PrintStream outputFile(String name) throws FileNotFoundException { File logFile = new File(name); File parentFile = logFile.getParentFile(); if (!parentFile.exists() && !parentFile.mkdirs()) { logger.error("Failed to create parent folder [" + parentFile.getAbsolutePath() + "] for log file [" + name + "]"); } return new PrintStream(new BufferedOutputStream(new FileOutputStream(name))); } private boolean isMavenConfigured() { return (mavenHome != null) && !mavenHome.isEmpty(); } private URL[] getUrls(File libDir) throws MalformedURLException { File[] mavenJars = libDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith("jar"); } }); URL[] mavenJarUrls = new URL[mavenJars.length]; for (int i = 0; i < mavenJarUrls.length; i++) { mavenJarUrls[i] = mavenJars[i].toURI().toURL(); } return mavenJarUrls; } @Override public Set<String> getDependencies(Set<String> resources) { Set<String> resolvedResources = new HashSet<>(resources.size()); for (String resource : resources) { String[] gav = extractGav(resource); List<String> dependencyList; try { String dependencyFilePath = getResourceFolderPath(gav) + SEPARATOR + getPathFileName(gav); File file = new File(dependencyFilePath); if (!file.exists()) { lock.lock(); try { //double check if file was just created if (!file.exists()) { buildDependencyFile(gav); } } finally { lock.unlock(); } } dependencyList = parse(file); resolvedResources.addAll(dependencyList); } catch (IOException e) { throw new IllegalStateException(e); } } return resolvedResources; } @SuppressWarnings("ConstantConditions") private void buildDependencyFile(String[] gav) { String pomFilePath = getPomFilePath(gav); downloadArtifacts(gav); System.setProperty(MavenConfig.MAVEN_MDEP_OUTPUT_FILE_PROPEPRTY, getPathFileName(gav)); System.setProperty(MavenConfig.MAVEN_MDEP_PATH_SEPARATOR_PROPERTY, PATH_FILE_DELIMITER); System.setProperty(MavenConfig.MAVEN_CLASSWORLDS_CONF_PROPERTY, System.getProperty(MavenConfig.MAVEN_M2_CONF_PATH)); String[] args = new String[] { MavenConfig.MAVEN_SETTINGS_FILE_FLAG, System.getProperty(MavenConfig.MAVEN_SETTINGS_PATH), MavenConfig.MAVEN_POM_PATH_PROPERTY, pomFilePath, MavenConfig.DEPENDENCY_BUILD_CLASSPATH_COMMAND, MavenConfig.LOG_FILE_FLAG, constructGavLogFilePath(gav, "build") }; try { invokeMavenLauncher(args); } catch (Exception e) { throw new IllegalStateException("Failed to build classpath using Maven", e); } File fileToReturn = new File(getResourceFolderPath(gav) + SEPARATOR + getPathFileName(gav)); if (!fileToReturn.exists()) { throw new IllegalStateException(fileToReturn.getPath() + " not found"); } appendSelfToPathFile(gav, fileToReturn); } private String getPomFilePath(String[] gav) { return getResourceFolderPath(gav) + SEPARATOR + getFileName(gav, MavenConfig.POM_EXTENSION); } private String constructGavLogFilePath(String[] gav, String what) { return new File(mavenLogFolder, gav[0] + GAV_SEPARATOR + gav[1] + GAV_SEPARATOR + gav[2] + GAV_SEPARATOR + what + ".log") .getAbsolutePath(); } private void invokeMavenLauncher(String[] args) throws Exception { ClassLoader origCL = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(mavenClassLoader); try { int exitCode = (Integer) MAVEN_EXECUTE_METHOD.invoke(null, new Object[] { args }); if (exitCode != 0) { throw new RuntimeException("mvn " + StringUtils.arrayToDelimitedString(args, " ") + " returned " + exitCode + ", see log for details"); } } finally { Thread.currentThread().setContextClassLoader(origCL); } } private void downloadArtifacts(String[] gav) { getDependencies(gav, false); getDependencies(gav, true); } private void getDependencies(String[] gav, Boolean transitive) { System.setProperty(MavenConfig.MAVEN_ARTIFACT_PROPERTY, getResourceString(gav, transitive)); System.setProperty(MavenConfig.MAVEN_CLASSWORLDS_CONF_PROPERTY, System.getProperty(MavenConfig.MAVEN_M2_CONF_PATH)); System.setProperty(MavenConfig.TRANSITIVE_PROPERTY, transitive.toString()); String[] args = new String[] { MavenConfig.MAVEN_SETTINGS_FILE_FLAG, System.getProperty(MavenConfig.MAVEN_SETTINGS_PATH), MavenConfig.DEPENDENCY_GET_COMMAND, MavenConfig.LOG_FILE_FLAG, constructGavLogFilePath(gav, "get") }; try { invokeMavenLauncher(args); if (!transitive) { removeTestScopeDependencies(gav); } } catch (Exception e) { throw new IllegalStateException("Failed to download resources using Maven", e); } finally { System.getProperties().remove(MavenConfig.TRANSITIVE_PROPERTY); } } private void removeTestScopeDependencies(String[] gav) { String pomFilePath = getPomFilePath(gav); try { removeByXpathExpression(pomFilePath, "/project/dependencies/dependency[scope[contains(text(), 'test')]]"); removeByXpathExpression(pomFilePath, "/project/dependencyManagement/dependencies/dependency[scope[contains(text(), 'test')]]"); } catch (Exception e) { throw new RuntimeException(e); } } private void removeByXpathExpression(String pomFilePath, String expression) throws SAXException, IOException, ParserConfigurationException, XPathExpressionException, TransformerException { File xmlFile = new File(pomFilePath); Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(xmlFile); XPath xpath = XPathFactory.newInstance().newXPath(); NodeList nl = (NodeList) xpath.compile(expression).evaluate(doc, XPathConstants.NODESET); if (nl != null && nl.getLength() > 0) { for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); node.getParentNode().removeChild(node); } Transformer transformer = TransformerFactory.newInstance().newTransformer(); // need to convert to file and then to path to override a problem with spaces Result output = new StreamResult(new File(pomFilePath).getPath()); Source input = new DOMSource(doc); transformer.transform(input, output); } } private void appendSelfToPathFile(String[] gav, File pathFile) { File resourceFolder = new File(getResourceFolderPath(gav)); if (!resourceFolder.exists() || !resourceFolder.isDirectory()) { throw new IllegalStateException("Directory " + resourceFolder.getPath() + " not found"); } File[] files = resourceFolder.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".jar") || name.toLowerCase().endsWith(".zip"); } }); //we suppose that there should be either 1 jar or 1 zip if (files.length == 0) { throw new IllegalStateException("No resource is found in " + resourceFolder.getPath()); } File resourceFile = files[0]; try (FileWriter fw = new FileWriter(pathFile, true); BufferedWriter bw = new BufferedWriter(fw); PrintWriter out = new PrintWriter(bw)) { out.print(PATH_FILE_DELIMITER); out.print(resourceFile.getCanonicalPath()); } catch (IOException e) { throw new IllegalStateException("Failed to append to file " + pathFile.getParent(), e); } } private String getResourceString(String[] gav, boolean transitive) { //if not transitive, use type "pom" String[] newGav = new String[Math.max(4, gav.length)]; System.arraycopy(gav, 0, newGav, 0, gav.length); if (!transitive) { newGav[3] = "pom"; } else { if (newGav[3] == null) { newGav[3] = "jar"; } } return StringUtils.arrayToDelimitedString(newGav, GAV_DELIMITER); } private String getPathFileName(String[] gav) { return getFileName(gav, PATH_FILE_EXTENSION); } private String getFileName(String[] gav, String extension) { return getArtifactID(gav) + '-' + getVersion(gav) + "." + extension; } private List<String> parse(File file) throws IOException { try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) { String line = reader.readLine(); if (line.startsWith(PATH_FILE_DELIMITER)) { line = line.substring(PATH_FILE_DELIMITER.length()); } String[] paths = line.split(PATH_FILE_DELIMITER); return Arrays.asList(paths); } } private String getResourceFolderPath(String[] gav) { return mavenConfig.getLocalMavenRepoPath() + SEPARATOR + getGroupIDPath(gav) + SEPARATOR + getArtifactID(gav) + SEPARATOR + getVersion(gav); } private String[] extractGav(String resource) { String[] gav = resource.split(GAV_DELIMITER); if ((gav.length < MINIMAL_GAV_PARTS) || (gav.length > MAXIMAL_GAV_PARTS)) {//at least g:a:v at maximum g:a:v:p:c throw new IllegalArgumentException("Unexpected resource format: " + resource + ", should be <group ID>:<artifact ID>:<version> or <group ID>:<artifact ID>:<version>:<packaging> or <group ID>:<artifact ID>:<version>:<packaging>:<classifier>"); } return gav; } private String getGroupIDPath(String[] gav) { return gav[0].replace('.', SEPARATOR); } private String getArtifactID(String[] gav) { return gav[1]; } private String getVersion(String[] gav) { return gav[2]; } }