com.itemis.maven.plugins.unleash.util.PomUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of unleash-maven-plugin Show documentation
Show all versions of unleash-maven-plugin Show documentation
This plugin provides a generic alternative to the error-prone default release plugin provided by Maven.
It is designed to require a minimal effort of work for releasing modules and being extensible to integrate in every
project setup.
package com.itemis.maven.plugins.unleash.util;
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.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
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 com.google.common.base.Joiner;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.Parent;
import org.apache.maven.project.MavenProject;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.StandardSystemProperty;
import com.google.common.io.Closeables;
import com.itemis.maven.plugins.unleash.util.functions.ProjectToString;
/**
* Offers utility methods for POM parsing and writing (from/to DOM documents) as well as common POM manipulation
* features working on DOM level.
*
* @author Stanley Hillner
* @since 1.0.0
*/
public final class PomUtil {
public static final String ARTIFACT_TYPE_JAR = "jar";
public static final String ARTIFACT_TYPE_POM = "pom";
public static final String VERSION_QUALIFIER_SNAPSHOT = "-SNAPSHOT";
public static final String VERSION_LATEST = "LATEST";
// DOM node names
public static final String NODE_NAME_ARTIFACT_ID = "artifactId";
public static final String NODE_NAME_BUILD = "build";
public static final String NODE_NAME_EXECUTION = "execution";
public static final String NODE_NAME_EXECUTIONS = "executions";
public static final String NODE_NAME_GOAL = "goal";
public static final String NODE_NAME_GOALS = "goals";
public static final String NODE_NAME_GROUP_ID = "groupId";
public static final String NODE_NAME_ID = "id";
public static final String NODE_NAME_PARENT = "parent";
public static final String NODE_NAME_PHASE = "phase";
public static final String NODE_NAME_PLUGIN = "plugin";
public static final String NODE_NAME_PLUGINS = "plugins";
public static final String NODE_NAME_PROJECT = "project";
public static final String NODE_NAME_SCM = "scm";
public static final String NODE_NAME_SCM_CONNECTION = "connection";
public static final String NODE_NAME_SCM_DEV_CONNECTION = "developerConnection";
public static final String NODE_NAME_SCM_TAG = "tag";
public static final String NODE_NAME_SCM_URL = "url";
public static final String NODE_NAME_VERSION = "version";
public static final String NODE_NAME_DEPENDENCY = "dependency";
public static final String NODE_NAME_DEPENDENCIES = "dependencies";
public static final String NODE_NAME_DEPENDENCY_MANAGEMENT = "dependencyManagement";
private static final byte[] LINE_SEPERATOR = StandardSystemProperty.LINE_SEPARATOR.value().getBytes();
private PomUtil() {
// Should not be instanciated
}
/**
* Parses the POM file from which the project was loaded into a {@link Document} for further manipulation.
*
* @param project the project from which the parser retrieves the POM file.
* @return the parsed document for further manipulation.
*/
public static final Optional parsePOM(MavenProject project) {
try {
return parsePOM(project.getFile());
} catch (RuntimeException e) {
throw new RuntimeException(
"Could not load the project object model of the following module: " + ProjectToString.INSTANCE.apply(project),
e);
}
}
/**
* Assumes that the passed file is a Maven POM file and parses it into a {@link Document} for further manipulation.
*
* @param pomFile the pom file to be parsed.
* @return the parsed document for further manipulation.
*/
public static final Optional parsePOM(File pomFile) {
// adaption for https://github.com/shillner/unleash-maven-plugin/issues/98
// in case of pom-less tycho builds the modules don't have POMs
if (pomFile == null || !pomFile.exists()) {
return Optional.absent();
}
Preconditions.checkArgument(pomFile.isFile(), "The project file does not exist or is invalid.");
try {
return Optional.of(parsePOM(new FileInputStream(pomFile)));
} catch (FileNotFoundException e) {
throw new RuntimeException("Could not load the project object model from file: " + pomFile.getAbsolutePath(), e);
}
}
/**
* Assumes that the passed input Stream contains content describing a POM and parses the content into a
* {@link Document} for further manipulation.
*
* @param in the stream to be parsed. This stream will be closed after parsing the document.
* @return the parsed document for further manipulation.
*/
public static final Document parsePOM(InputStream in) {
try {
DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document document = documentBuilder.parse(in);
return document;
} catch (Exception e) {
throw new RuntimeException("Could not load the project object model from input stream.", e);
} finally {
Closeables.closeQuietly(in);
}
}
/**
* Serializes the passed document which should contain POM content to the project file of the passed Maven project.
*
* @param document the document to be serialized.
* @param project the project from which the serialization target will be retrieved.
*/
public static final void writePOM(Document document, MavenProject project) {
File pom = project.getFile();
Preconditions.checkArgument(pom != null && pom.exists() && pom.isFile(),
"The passed project does not contain a valid POM file reference.");
try {
writePOM(document, new FileOutputStream(pom), true);
} catch (Throwable t) {
throw new RuntimeException("Could not serialize the project object model of the following module: "
+ ProjectToString.INSTANCE.apply(project), t);
}
}
/**
* Serializes the passed document which should contain POM content to the passed output stream.
*
* @param document the document to be serialized.
* @param out the output stream where the document shall be written to.
* @param closeOut whether to close the output stream afterwards or not.
*/
public static final void writePOM(Document document, OutputStream out, boolean closeOut) {
try {
Transformer transformer = TransformerFactory.newInstance().newTransformer();
DOMSource source = new DOMSource(document);
transformer.transform(source, new StreamResult(out));
// append newline to end-of-file
// is there any elegant way to do that on Document or in Transformer?
out.write(LINE_SEPERATOR);
} catch (Exception e) {
throw new RuntimeException("Could not serialize the project object model to given output stream.", e);
} finally {
if (closeOut) {
try {
Closeables.close(out, true);
} catch (IOException e) {
throw new RuntimeException("Actually this should not happen :(", e);
}
}
}
}
/**
* Changes the project version of the POM as well as directly in the XML document preserving the whole document
* formatting.
*
* @param model the POM where to adapt the project version.
* @param document the POM as an XML document in which the project version shall be adapted.
* @param newVersion the new project version to set.
*/
public static void setProjectVersion(Model model, Document document, String newVersion) {
Preconditions.checkArgument(hasChildNode(document, NODE_NAME_PROJECT),
"The document doesn't seem to be a POM model, project element is missing.");
// if model version is null, the parent version is inherited
if (model.getVersion() != null) {
// first step: update the version of the in-memory project
model.setVersion(newVersion);
// second step: update the project version in the DOM document that is then serialized for later building
NodeList children = document.getDocumentElement().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (Objects.equal(child.getNodeName(), PomUtil.NODE_NAME_VERSION)) {
child.setTextContent(newVersion);
}
}
}
}
/**
* Changes the project's parent version of the POM as well as directly in the XML document preserving the whole
* document formatting.
*
* @param model the POM where to adapt the project's parent version.
* @param document the POM as an XML document in which the project's parent version shall be adapted.
* @param newParentVersion the new version to set for the project parent.
*/
public static void setParentVersion(Model model, Document document, String newParentVersion) {
Preconditions.checkArgument(hasChildNode(document, NODE_NAME_PROJECT),
"The document doesn't seem to be a POM model, project element is missing.");
// first step: update parent version of the in-memory model
Parent parent = model.getParent();
if (parent != null) {
parent.setVersion(newParentVersion);
}
// second step: update the parent version in the DOM document that will be serialized for later building
Node parentNode = document.getDocumentElement().getElementsByTagName(PomUtil.NODE_NAME_PARENT).item(0);
if (parentNode != null) {
NodeList children = parentNode.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (Objects.equal(child.getNodeName(), PomUtil.NODE_NAME_VERSION)) {
child.setTextContent(newParentVersion);
}
}
}
}
/**
* Changes the reactor dependency version of the POM as well as directly in the XML document preserving the whole
* document formatting.
*
* @param dependency the reactor dependency where to adapt the project version.
* @param document the POM as an XML document in which the project version shall be adapted.
* @param dependenciesPath the XPath to the {@code dependencies} starting from {@code /project/}.
* e.g. {@code /profiles/profile[id[text()='release']]/dependencyManagement}.
* @param newVersion the new dependency version to set.
*/
public static void setDependencyVersion(Dependency dependency, Document document, String dependenciesPath,
String newVersion) {
Preconditions.checkArgument(hasChildNode(document, NODE_NAME_PROJECT),
"The document doesn't seem to be a POM model, project element is missing.");
// first step: update dependency version of the in-memory model
dependency.setVersion(newVersion);
// second step: update the dependency version in the DOM document that is then serialized for later building
NodeList dependencyNodes;
try {
List xPathParts = new ArrayList<>();
xPathParts.add(NODE_NAME_PROJECT);
if (dependenciesPath.startsWith("/")) {
dependenciesPath = dependenciesPath.substring("/".length());
}
if (!dependenciesPath.isEmpty()) {
xPathParts.add(dependenciesPath);
}
xPathParts.add(NODE_NAME_DEPENDENCIES);
xPathParts.add(NODE_NAME_DEPENDENCY + "[groupId[text()='" + dependency.getGroupId() + "']"
+ " and artifactId[text()='" + dependency.getArtifactId() + "']]");
String expression = "/" + Joiner.on("/").skipNulls().join(xPathParts);
XPath xPath = XPathFactory.newInstance().newXPath();
dependencyNodes = (NodeList) xPath.evaluate(expression, document.getDocumentElement(), XPathConstants.NODESET);
} catch (XPathExpressionException e) {
String message = "Cannot evaluate xPath against '" + dependenciesPath + "'.";
throw new RuntimeException(message, e);
}
for (int i = 0; i < dependencyNodes.getLength(); i++) {
Node dependencyNode = dependencyNodes.item(i);
NodeList childNodes = dependencyNode.getChildNodes();
for (int j = 0; j < childNodes.getLength(); j++) {
Node childNode = childNodes.item(j);
if (Objects.equal(childNode.getNodeName(), PomUtil.NODE_NAME_VERSION)) {
childNode.setTextContent(newVersion);
}
}
}
}
/**
* Queries for the project build node and creates one on demand.
*
* @param document the document from which to get the node or where to create the node at.
* @param createOnDemand {@code true} if the build node shall be created when not present.
* @return the build node of the document or {@code null} if the node doesn't exist and {@code createOnDemand} was set
* to {@code false}.
*/
public static Node getOrCreateBuildNode(Document document, boolean createOnDemand) {
Preconditions.checkArgument(hasChildNode(document, NODE_NAME_PROJECT),
"The document doesn't seem to be a POM model, project element is missing.");
Node build = null;
Node project = document.getElementsByTagName(NODE_NAME_PROJECT).item(0);
NodeList children = project.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (Objects.equal(NODE_NAME_BUILD, node.getNodeName())) {
build = node;
break;
}
}
if (build == null && createOnDemand) {
build = document.createElement(NODE_NAME_BUILD);
project.appendChild(build);
}
return build;
}
/**
* Queries for the project build plugins node and creates one on demand. Creation is cascaded backwards.
*
* @param document the document from which to get the node or where to create the node at.
* @param createOnDemand {@code true} if the plugins node and parents shall be created when not present.
* @return the plugins node of the document or {@code null} if the node doesn't exist and {@code createOnDemand} was
* set to {@code false}.
*/
public static Node getOrCreatePluginsNode(Document document, boolean createOnDemand) {
Preconditions.checkArgument(hasChildNode(document, NODE_NAME_PROJECT),
"The document doesn't seem to be a POM model, project element is missing.");
Node build = getOrCreateBuildNode(document, createOnDemand);
Node plugins = null;
if (build != null) {
NodeList children = build.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (Objects.equal(NODE_NAME_PLUGINS, node.getNodeName())) {
plugins = node;
break;
}
}
if (plugins == null && createOnDemand) {
plugins = document.createElement(NODE_NAME_PLUGINS);
build.appendChild(plugins);
}
}
return plugins;
}
/**
* Queries the document for a specific plugin node which is identified by its groupId and artifactId.
*
* @param document the document from which the plugin shall be retrieved if one is configured.
* @param groupId the groupId of the searched plugin.
* @param artifactId the artifactId of the searched plugin.
* @return the queried plugin node or {@code null} if none is configured.
*/
public static Node getPlugin(Document document, String groupId, String artifactId) {
Preconditions.checkArgument(hasChildNode(document, NODE_NAME_PROJECT),
"The document doesn't seem to be a POM model, project element is missing.");
Node pluginsNode = getOrCreatePluginsNode(document, false);
if (pluginsNode != null) {
NodeList plugins = pluginsNode.getChildNodes();
for (int i = 0; i < plugins.getLength(); i++) {
Node plugin = plugins.item(i);
if (Objects.equal(NODE_NAME_PLUGIN, plugin.getNodeName())) {
NodeList pluginSettings = plugin.getChildNodes();
boolean gidMatches = false;
boolean aidMatches = false;
for (int j = 0; j < pluginSettings.getLength(); j++) {
Node setting = pluginSettings.item(j);
if (Objects.equal(NODE_NAME_GROUP_ID, setting.getNodeName())) {
if (Objects.equal(groupId, setting.getTextContent())) {
gidMatches = true;
}
} else if (Objects.equal(NODE_NAME_ARTIFACT_ID, setting.getNodeName())) {
if (Objects.equal(artifactId, setting.getTextContent())) {
aidMatches = true;
}
}
if (gidMatches && aidMatches) {
return plugin;
}
}
}
}
}
return null;
}
/**
* Creates a plugin node in the given POM document or returns an already existing plugin node with the passed
* coordinates.
*
* @param document the document where to create the plugin at.
* @param groupId the groupId of the plugin to be created.
* @param artifactId the artifactId of the plugin to be created.
* @param version the version of the plugin to be created.
* @return the node representing this plugin.
*/
public static Node createPlugin(Document document, String groupId, String artifactId, String version) {
Preconditions.checkArgument(hasChildNode(document, NODE_NAME_PROJECT),
"The document doesn't seem to be a POM model, project element is missing.");
Node plugins = getOrCreatePluginsNode(document, true);
Node existingPlugin = getPlugin(document, groupId, artifactId);
if (existingPlugin != null) {
if (!hasChildNode(existingPlugin, NODE_NAME_VERSION)) {
Element ver = document.createElement(NODE_NAME_VERSION);
ver.setTextContent(version);
existingPlugin.appendChild(ver);
}
return existingPlugin;
} else {
Element plugin = document.createElement(NODE_NAME_PLUGIN);
plugins.appendChild(plugin);
Element gid = document.createElement(NODE_NAME_GROUP_ID);
gid.setTextContent(groupId);
plugin.appendChild(gid);
Element aid = document.createElement(NODE_NAME_ARTIFACT_ID);
aid.setTextContent(artifactId);
plugin.appendChild(aid);
Element ver = document.createElement(NODE_NAME_VERSION);
ver.setTextContent(version);
plugin.appendChild(ver);
return plugin;
}
}
/**
* Creates a new execution element under the given plugin node.
*
* @param plugin the plugin under which the new execution shall be created.
* @param id the execution id.
* @param phase the phase in which the execution shall run.
* @param goals the goals to be executed.
* @return the freshly created execution element.
*/
public static Node createPluginExecution(Node plugin, String id, Optional phase, String... goals) {
Document document = plugin.getOwnerDocument();
Node executions = null;
NodeList children = plugin.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (Objects.equal(NODE_NAME_EXECUTIONS, child.getNodeName())) {
executions = child;
break;
}
}
if (executions == null) {
executions = document.createElement(NODE_NAME_EXECUTIONS);
plugin.appendChild(executions);
}
Element execution = document.createElement(NODE_NAME_EXECUTION);
executions.appendChild(execution);
Element idNode = document.createElement(NODE_NAME_ID);
idNode.setTextContent(id);
execution.appendChild(idNode);
if (phase.isPresent()) {
Element phaseNode = document.createElement(NODE_NAME_PHASE);
phaseNode.setTextContent(phase.get());
execution.appendChild(phaseNode);
}
if (goals.length > 0) {
Element goalsNode = document.createElement(NODE_NAME_GOALS);
execution.appendChild(goalsNode);
for (String goal : goals) {
Element goalNode = document.createElement(NODE_NAME_GOAL);
goalNode.setTextContent(goal);
goalsNode.appendChild(goalNode);
}
}
return execution;
}
/**
* Queries the document for an existing SCM node and creates one on demand if requested.
*
* @param document the document to query.
* @param createOnDemand if {@code true} the node will be created on demand.
* @return the SCM node of the document or {@code null} if the node doesn't exist and {@code createOnDemand} was set
* to {@code false}.
*/
public static Node getOrCreateScmNode(Document document, boolean createOnDemand) {
Preconditions.checkArgument(hasChildNode(document, NODE_NAME_PROJECT),
"The document doesn't seem to be a POM model, project element is missing.");
NodeList scmNodeList = document.getElementsByTagName(NODE_NAME_SCM);
Node scm = null;
if (scmNodeList.getLength() == 0 && createOnDemand) {
scm = document.createElement(NODE_NAME_SCM);
document.getDocumentElement().appendChild(scm);
} else {
scm = scmNodeList.item(0);
}
return scm;
}
/**
* Sets the text content of the given node to the specified value preserving all whitespace.
*
* @param parentNode the parent node of the node where to set the text content.
* @param nodeName the name of the node to set the text content at.
* @param content the text content to set.
* @param createOnDemand if {@code true} the node with name {@code nodeName} will be created as a child of
* {@code parentNode} if it does not exist.
*/
public static void setNodeTextContent(Node parentNode, String nodeName, String content, boolean createOnDemand) {
Node node = null;
NodeList children = parentNode.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node n = children.item(i);
if (Objects.equal(nodeName, n.getNodeName())) {
node = n;
break;
}
}
if (node == null && createOnDemand) {
node = parentNode.getOwnerDocument().createElement(nodeName);
if (children.getLength() > 0) {
Node lastChild = children.item(children.getLength() - 1);
Text lineBreak = parentNode.getOwnerDocument().createTextNode("\n");
parentNode.insertBefore(lineBreak, lastChild);
parentNode.insertBefore(node, lastChild);
} else {
parentNode.appendChild(node);
}
}
if (node != null) {
node.setTextContent(content);
}
}
/**
* Deletes the specified node from the given parent.
*
* @param parentNode the parent of the node to delete.
* @param nodeName the name of the node to delete.
*/
public static void deleteNode(Node parentNode, String nodeName) {
Node nodeToDelete = null;
NodeList children = parentNode.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node n = children.item(i);
if (Objects.equal(nodeName, n.getNodeName())) {
nodeToDelete = n;
break;
}
}
if (nodeToDelete != null) {
parentNode.removeChild(nodeToDelete);
}
}
/**
* Queries a node for a child with a specific name.
*
* @param parentNode the parent to query for the node.
* @param nodeName the name of the searched node.
* @return {@code true} if the parent contains a node with the specified name.
*/
public static boolean hasChildNode(Node parentNode, String nodeName) {
NodeList children = parentNode.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node n = children.item(i);
if (Objects.equal(nodeName, n.getNodeName())) {
return true;
}
}
return false;
}
/**
* Get the text content of a specified child node (the first one) if it is present.
*
* @param parentNode the parent to query for the node.
* @param nodeName the name of the child node from which the text content is requested.
* @return the text content of the searched node.
*/
public static Optional getChildNodeTextContent(Node parentNode, String nodeName) {
String value = null;
NodeList children = parentNode.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node n = children.item(i);
if (Objects.equal(nodeName, n.getNodeName())) {
value = n.getTextContent();
break;
}
}
return Optional.fromNullable(value);
}
}