/** * Copyright (C) 2015 Michael Schnell. All rights reserved. * http://www.fuin.org/ * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) any * later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see http://www.gnu.org/licenses/. */ package org.fuin.utils4maven; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.nio.charset.Charset; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.maven.model.Dependency; import org.apache.maven.model.DependencyManagement; import org.apache.maven.model.Model; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import org.apache.maven.model.merge.ModelMerger; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; import org.fuin.utils4j.Utils4J; import org.jboss.shrinkwrap.resolver.api.NoResolvedResultException; import org.jboss.shrinkwrap.resolver.api.maven.Maven; import org.jboss.shrinkwrap.resolver.api.maven.MavenFormatStage; import org.jboss.shrinkwrap.resolver.api.maven.MavenStrategyStage; /** * Reads a Maven POM. */ public final class MavenPomReader { private MavenPomReader() { throw new UnsupportedOperationException( "Creating an instance is not supported for this utility class"); } /** * Reads a Maven POM. * * @param canonicalForm * Maven "group:artifact:version". * * @return Model created from POM and Super-POM. */ public static Model readModel(final String canonicalForm) { final File artifactFile = resolveArtifact(canonicalForm); final File pomXmlFile = new File( FilenameUtils.removeExtension(artifactFile.toString()) + ".pom"); try { final Model pre = readModel(pomXmlFile); final Model model = readModel(pomXmlFile, pre); final Model resultModel; if (model.getParent() == null) { resultModel = model; } else { final String parentGroupId = model.getParent().getGroupId(); final String parentArtifactId = model.getParent() .getArtifactId(); final String parentVersion = model.getParent().getVersion(); final Model parentModel = readModel(parentGroupId + ":" + parentArtifactId + ":pom:" + parentVersion); resultModel = merge(parentModel, model); } validate(resultModel); return resultModel; } catch (XmlPullParserException ex) { throw new RuntimeException("Error parsing POM [" + pomXmlFile + ", Requested Version='" + canonicalForm + "']!", ex); } catch (final IOException ex) { throw new RuntimeException("Error reading POM [" + pomXmlFile + ", Requested Version='" + canonicalForm + "']!", ex); } } private static Model readModel(final File pomXmlFile) throws IOException, XmlPullParserException { return readModel(pomXmlFile, null); } private static Model readModel(final File pomXmlFile, final Model preModel) throws IOException, XmlPullParserException { final Reader reader; if (preModel == null) { reader = new FileReader(pomXmlFile); } else { final File varReplacedPomXmlFile = replaceVars(pomXmlFile, preModel); reader = new FileReader(varReplacedPomXmlFile); } final Model model; try { final MavenXpp3Reader xpp3Reader = new MavenXpp3Reader(); model = xpp3Reader.read(reader); } finally { reader.close(); } if (model.getVersion() == null) { if (model.getParent() == null) { throw new IllegalStateException( "Version of model is null and no parent is avaliable: " + pomXmlFile); } if (model.getParent().getVersion() == null) { throw new IllegalStateException("Version of parent is null: " + pomXmlFile); } model.setVersion(model.getParent().getVersion()); if (model.getVersion() == null) { throw new IllegalStateException("Parent as also no version: " + pomXmlFile); } } return model; } private static File replaceVars(final File pomXmlFile, final Model model) { final Map<String, Object> vars = new HashMap<>(); vars.put("project", model); addAllModelProperties(vars, model); try { final String pomXml = FileUtils.readFileToString(pomXmlFile, "ISO-8859-1"); final String replacedPomXml = Utils4J.replaceVars(pomXml, vars); final File targetFile = new File(Utils4J.getTempDir(), "tmp-pom.xml"); FileUtils.write(targetFile, replacedPomXml, Charset.forName("ISO-8859-1")); return targetFile; } catch (final IOException ex) { throw new RuntimeException(ex); } } /** * Adds all model properties to the map. * * @param vars * Variables to add properties to. * @param model * Model to add properties from. */ public static void addAllModelProperties(final Map<String, Object> vars, final Model model) { final Enumeration<Object> enu = model.getProperties().keys(); while (enu.hasMoreElements()) { final String key = "" + enu.nextElement(); vars.put(key, "" + model.getProperties().get(key)); } } private static void validate(final Model model) { final List<Dependency> dependencies = model.getDependencies(); for (final Dependency dependency : dependencies) { if (dependency.getVersion() == null) { final DependencyManagement dm = model.getDependencyManagement(); final String version = findVersion(dm, dependency); if (version == null) { throw new IllegalStateException( "Dependency version not set for '" + dependency.getGroupId() + ":" + dependency.getArtifactId() + "' in '" + model.getGroupId() + ":" + model.getArtifactId() + ":" + model.getVersion() + "'"); } dependency.setVersion(Utils4J.replaceVars(version, model.getProperties())); } } } private static String findVersion(final DependencyManagement dm, final Dependency dep) { final List<Dependency> dependencies = dm.getDependencies(); for (final Dependency dependency : dependencies) { if ((dependency.getGroupId().equals(dep.getGroupId())) && (dependency.getArtifactId().equals(dep.getArtifactId()))) { return dependency.getVersion(); } } return null; } private static Model merge(final Model parent, final Model child) { final Model newModel = new Model(); final ModelMerger merger = new ModelMerger(); final Map<Object, Object> hints = new HashMap<Object, Object>(); merger.merge(newModel, parent, true, hints); merger.merge(newModel, child, true, hints); return newModel; } private static void loadPom(final String canonicalForm) { try { final MavenStrategyStage strategyStage = Maven.resolver().resolve( canonicalForm); final MavenFormatStage formatStage = strategyStage .withoutTransitivity(); formatStage.asSingleFile(); } catch (final NoResolvedResultException ex) { // Ignored by intention System.currentTimeMillis(); // Satisfy Checkstyle } } private static File resolveArtifact(final String canonicalForm) { try { if (canonicalForm.contains(":pom:")) { // Workaround for POM bug // https://issues.jboss.org/browse/SHRINKRES-214 // Makes sure that the POM file is loaded loadPom(canonicalForm); // Resolve any JAR file to get the local repository path final String jarNameAndPath = resolveArtifact( "org.apache.maven:maven-core:jar:3.2.5") .getCanonicalPath(); final int len = "org/apache/maven/maven-core/3.2.5/maven-core-3.2.5.jar" .length(); // Create the local POM file path and name final String filename = jarNameAndPath.substring(0, jarNameAndPath.length() - len) + "/" + canonicalToPath(canonicalForm); return new File(filename); } else { final MavenStrategyStage strategyStage = Maven.resolver() .resolve(canonicalForm); final MavenFormatStage formatStage = strategyStage .withoutTransitivity(); return formatStage.asSingleFile(); } } catch (final NoResolvedResultException ex) { throw new IllegalStateException("Couldn't resolve '" + canonicalForm + "'", ex); } catch (final IOException ex) { throw new IllegalStateException("Couldn't resolve '" + canonicalForm + "'", ex); } } private static String canonicalToPath(final String canonicalForm) { final StringTokenizer tok = new StringTokenizer(canonicalForm, ":'"); if (tok.countTokens() != 4) { throw new IllegalArgumentException("Invalid canonical form: '" + canonicalForm + "'"); } final String groupId = tok.nextToken(); final String artifactId = tok.nextToken(); final String type = tok.nextToken(); final String version = tok.nextToken(); return groupId.replace('.', '/') + "/" + artifactId + "/" + version + "/" + artifactId + "-" + version + "." + type; } }