blob: fb15e23664969f276f793e4a319536dc53a6145d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.ivy.plugins.parser.m2;
import org.apache.ivy.core.IvyPatternHelper;
import org.apache.ivy.core.module.descriptor.License;
import org.apache.ivy.core.module.id.ModuleId;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.plugins.repository.Resource;
import org.apache.ivy.util.XMLHelper;
import org.apache.ivy.util.url.URLHandlerRegistry;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* Provides the method to read some data out of the DOM tree of a pom file.
*/
public class PomReader {
private static final String PROFILES_ELEMENT = "profiles";
private static final String PACKAGING = "packaging";
private static final String DEPENDENCY = "dependency";
private static final String DEPENDENCIES = "dependencies";
private static final String DEPENDENCY_MGT = "dependencyManagement";
private static final String PROJECT = "project";
private static final String MODEL = "model";
private static final String GROUP_ID = "groupId";
private static final String ARTIFACT_ID = "artifactId";
private static final String VERSION = "version";
private static final String DESCRIPTION = "description";
private static final String HOMEPAGE = "url";
private static final String LICENSES = "licenses";
private static final String LICENSE = "license";
private static final String LICENSE_NAME = "name";
private static final String LICENSE_URL = "url";
private static final String PARENT = "parent";
private static final String SCOPE = "scope";
private static final String CLASSIFIER = "classifier";
private static final String OPTIONAL = "optional";
private static final String EXCLUSIONS = "exclusions";
private static final String EXCLUSION = "exclusion";
private static final String DISTRIBUTION_MGT = "distributionManagement";
private static final String RELOCATION = "relocation";
private static final String PROPERTIES = "properties";
private static final String PLUGINS = "plugins";
private static final String PLUGIN = "plugin";
private static final String TYPE = "type";
private static final String PROFILE = "profile";
private final Map<String, String> properties = new HashMap<>();
private final Element projectElement;
private final Element parentElement;
@SuppressWarnings("deprecation")
public PomReader(final URL descriptorURL, final Resource res) throws IOException, SAXException {
InputStream stream = new AddDTDFilterInputStream(
URLHandlerRegistry.getDefault().openStream(descriptorURL));
InputSource source = new InputSource(stream);
source.setSystemId(XMLHelper.toSystemId(descriptorURL));
try {
Document pomDomDoc = XMLHelper.parseToDom(source, new EntityResolver() {
public InputSource resolveEntity(String publicId, String systemId)
throws SAXException, IOException {
if (systemId != null && systemId.endsWith("m2-entities.ent")) {
return new InputSource(
PomReader.class.getResourceAsStream("m2-entities.ent"));
}
return null;
}
});
projectElement = pomDomDoc.getDocumentElement();
if (!PROJECT.equals(projectElement.getNodeName())
&& !MODEL.equals(projectElement.getNodeName())) {
throw new SAXParseException("project must be the root tag", res.getName(),
res.getName(), 0, 0);
}
parentElement = getFirstChildElement(projectElement, PARENT);
} finally {
try {
stream.close();
} catch (IOException e) {
// ignore
}
}
// Both environment and system properties take precedence over properties set in
// pom.xml. So we pre-populate our properties with the environment and system properties
// here
for (final Map.Entry<String, String> envEntry : System.getenv().entrySet()) {
// Maven let's users use "env." prefix for environment variables
this.setProperty("env." + envEntry.getKey(), envEntry.getValue());
}
// add system properties
final Properties sysProps = System.getProperties();
for (final String sysProp : sysProps.stringPropertyNames()) {
this.setProperty(sysProp, sysProps.getProperty(sysProp));
}
}
public boolean hasParent() {
return parentElement != null;
}
/**
* Add a property if not yet set and value is not null. This guarantees
* that property keeps the first value that is put on it and that the
* properties are never null.
*
* @param prop String
* @param val String
*/
public void setProperty(String prop, String val) {
if (!properties.containsKey(prop) && val != null) {
properties.put(prop, val);
}
}
public String getGroupId() {
String groupId = getFirstChildText(projectElement, GROUP_ID);
if (groupId == null) {
groupId = getFirstChildText(parentElement, GROUP_ID);
}
return replaceProps(groupId);
}
public String getParentGroupId() {
String groupId = getFirstChildText(parentElement, GROUP_ID);
if (groupId == null) {
groupId = getFirstChildText(projectElement, GROUP_ID);
}
return replaceProps(groupId);
}
public String getArtifactId() {
String val = getFirstChildText(projectElement, ARTIFACT_ID);
if (val == null) {
val = getFirstChildText(parentElement, ARTIFACT_ID);
}
return replaceProps(val);
}
public String getParentArtifactId() {
String val = getFirstChildText(parentElement, ARTIFACT_ID);
if (val == null) {
val = getFirstChildText(projectElement, ARTIFACT_ID);
}
return replaceProps(val);
}
public String getVersion() {
String val = getFirstChildText(projectElement, VERSION);
if (val == null) {
val = getFirstChildText(parentElement, VERSION);
}
return replaceProps(val);
}
public String getParentVersion() {
String val = getFirstChildText(parentElement, VERSION);
if (val == null) {
val = getFirstChildText(projectElement, VERSION);
}
return replaceProps(val);
}
public String getPackaging() {
final String val = getFirstChildText(projectElement, PACKAGING);
if (val == null) {
return "jar";
}
return replaceProps(val);
}
public String getHomePage() {
String val = getFirstChildText(projectElement, HOMEPAGE);
if (val == null) {
val = "";
}
return val;
}
public String getDescription() {
String val = getFirstChildText(projectElement, DESCRIPTION);
if (val == null) {
val = "";
}
return val.trim();
}
public License[] getLicenses() {
Element licenses = getFirstChildElement(projectElement, LICENSES);
if (licenses == null) {
return new License[0];
}
licenses.normalize();
List<License> lics = new ArrayList<>();
for (Element license : getAllChilds(licenses)) {
if (LICENSE.equals(license.getNodeName())) {
String name = getFirstChildText(license, LICENSE_NAME);
String url = getFirstChildText(license, LICENSE_URL);
if (name == null && url == null) {
// move to next license
continue;
}
if (name == null) {
// The license name is required in Ivy but not in a POM!
name = "Unknown License";
}
lics.add(new License(name, url));
}
}
return lics.toArray(new License[lics.size()]);
}
public ModuleRevisionId getRelocation() {
Element distrMgt = getFirstChildElement(projectElement, DISTRIBUTION_MGT);
Element relocation = getFirstChildElement(distrMgt, RELOCATION);
if (relocation == null) {
return null;
} else {
String relocGroupId = getFirstChildText(relocation, GROUP_ID);
if (relocGroupId == null) {
relocGroupId = getGroupId();
}
String relocArtId = getFirstChildText(relocation, ARTIFACT_ID);
if (relocArtId == null) {
relocArtId = getArtifactId();
}
String relocVersion = getFirstChildText(relocation, VERSION);
if (relocVersion == null) {
relocVersion = getVersion();
}
return ModuleRevisionId.newInstance(relocGroupId, relocArtId, relocVersion);
}
}
public List<PomDependencyData> getDependencies() {
return getDependencies(projectElement);
}
private List<PomDependencyData> getDependencies(Element parent) {
Element dependenciesElement = getFirstChildElement(parent, DEPENDENCIES);
if (dependenciesElement == null) {
return Collections.emptyList();
}
List<PomDependencyData> dependencies = new LinkedList<>();
NodeList children = dependenciesElement.getChildNodes();
for (int i = 0, sz = children.getLength(); i < sz; i++) {
Node node = children.item(i);
if (node instanceof Element && DEPENDENCY.equals(node.getNodeName())) {
dependencies.add(new PomDependencyData((Element) node));
}
}
return dependencies;
}
public List<PomDependencyMgt> getDependencyMgt() {
return getDependencyMgt(projectElement);
}
private List<PomDependencyMgt> getDependencyMgt(Element parent) {
Element dependenciesElement = getFirstChildElement(
getFirstChildElement(parent, DEPENDENCY_MGT), DEPENDENCIES);
if (dependenciesElement == null) {
return Collections.emptyList();
}
List<PomDependencyMgt> dependencies = new LinkedList<>();
NodeList children = dependenciesElement.getChildNodes();
for (int i = 0, sz = children.getLength(); i < sz; i++) {
Node node = children.item(i);
if (node instanceof Element && DEPENDENCY.equals(node.getNodeName())) {
dependencies.add(new PomDependencyMgtElement((Element) node));
}
}
return dependencies;
}
public List<PomProfileElement> getProfiles() {
Element profilesElement = getFirstChildElement(projectElement, PROFILES_ELEMENT);
if (profilesElement == null) {
return Collections.emptyList();
}
List<PomProfileElement> result = new LinkedList<>();
NodeList children = profilesElement.getChildNodes();
for (int i = 0, sz = children.getLength(); i < sz; i++) {
Node node = children.item(i);
if (node instanceof Element && PROFILE.equals(node.getNodeName())) {
result.add(new PomProfileElement((Element) node));
}
}
return result;
}
public class PomDependencyMgtElement implements PomDependencyMgt {
private final Element depElement;
public PomDependencyMgtElement(PomDependencyMgtElement copyFrom) {
this(copyFrom.depElement);
}
PomDependencyMgtElement(Element depElement) {
this.depElement = depElement;
}
/*
* (non-Javadoc)
*
* @see org.apache.ivy.plugins.parser.m2.PomDependencyMgt#getGroupId()
*/
public String getGroupId() {
String val = getFirstChildText(depElement, GROUP_ID);
return replaceProps(val);
}
/*
* (non-Javadoc)
*
* @see org.apache.ivy.plugins.parser.m2.PomDependencyMgt#getArtifaceId()
*/
public String getArtifactId() {
String val = getFirstChildText(depElement, ARTIFACT_ID);
return replaceProps(val);
}
/*
* (non-Javadoc)
*
* @see org.apache.ivy.plugins.parser.m2.PomDependencyMgt#getVersion()
*/
public String getVersion() {
String val = getFirstChildText(depElement, VERSION);
return replaceProps(val);
}
public String getScope() {
String val = getFirstChildText(depElement, SCOPE);
return replaceProps(val);
}
public List<ModuleId> getExcludedModules() {
Element exclusionsElement = getFirstChildElement(depElement, EXCLUSIONS);
if (exclusionsElement == null) {
return Collections.emptyList();
}
List<ModuleId> exclusions = new LinkedList<>();
NodeList children = exclusionsElement.getChildNodes();
for (int i = 0, sz = children.getLength(); i < sz; i++) {
Node node = children.item(i);
if (node instanceof Element && EXCLUSION.equals(node.getNodeName())) {
String groupId = getFirstChildText((Element) node, GROUP_ID);
String artifactId = getFirstChildText((Element) node, ARTIFACT_ID);
if (groupId != null && artifactId != null) {
exclusions.add(ModuleId.newInstance(groupId, artifactId));
}
}
}
return exclusions;
}
}
public List<PomPluginElement> getPlugins() {
return getPlugins(projectElement);
}
private List<PomPluginElement> getPlugins(Element parent) {
Element buildElement = getFirstChildElement(parent, "build");
Element pluginsElement = getFirstChildElement(buildElement, PLUGINS);
if (pluginsElement == null) {
return Collections.emptyList();
}
NodeList children = pluginsElement.getChildNodes();
List<PomPluginElement> plugins = new LinkedList<>();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node instanceof Element && PLUGIN.equals(node.getNodeName())) {
plugins.add(new PomPluginElement((Element) node));
}
}
return plugins;
}
private static Map<String, String> getProperties(final Element parent) {
final Element propsEl = getFirstChildElement(parent, PROPERTIES);
if (propsEl == null) {
return Collections.emptyMap();
}
propsEl.normalize();
final Map<String, String> props = new HashMap<>();
for (final Element prop : getAllChilds(propsEl)) {
props.put(prop.getNodeName(), getTextContent(prop));
}
return props;
}
public class PomPluginElement implements PomDependencyMgt {
private Element pluginElement;
PomPluginElement(Element pluginElement) {
this.pluginElement = pluginElement;
}
public String getGroupId() {
String val = getFirstChildText(pluginElement, GROUP_ID);
return replaceProps(val);
}
public String getArtifactId() {
String val = getFirstChildText(pluginElement, ARTIFACT_ID);
return replaceProps(val);
}
public String getVersion() {
String val = getFirstChildText(pluginElement, VERSION);
return replaceProps(val);
}
public String getScope() {
return null; // not used
}
public List<ModuleId> getExcludedModules() {
return Collections.emptyList(); // probably not used?
}
}
public class PomDependencyData extends PomDependencyMgtElement {
private final Element depElement;
public PomDependencyData(PomDependencyData copyFrom) {
this(copyFrom.depElement);
}
PomDependencyData(Element depElement) {
super(depElement);
this.depElement = depElement;
}
@Override
public String getScope() {
String val = getFirstChildText(depElement, SCOPE);
return replaceProps(val);
}
public String getClassifier() {
String val = getFirstChildText(depElement, CLASSIFIER);
return replaceProps(val);
}
public String getType() {
String val = getFirstChildText(depElement, TYPE);
return replaceProps(val);
}
public boolean isOptional() {
return Boolean.parseBoolean(getFirstChildText(depElement, OPTIONAL));
}
}
public class PomProfileElement {
private static final String VALUE = "value";
private static final String NAME = "name";
private static final String PROPERTY = "property";
private static final String ID_ELEMENT = "id";
private static final String ACTIVATION_ELEMENT = "activation";
private static final String ACTIVE_BY_DEFAULT_ELEMENT = "activeByDefault";
private static final String OS = "os";
private static final String FAMILY = "family";
private static final String VERSION = "version";
private static final String ARCH = "arch";
private static final String FILE = "file";
private static final String MISSING = "missing";
private static final String EXISTS = "exists";
private static final String JDK = "jdk";
private final Element profileElement;
PomProfileElement(Element profileElement) {
this.profileElement = profileElement;
}
public String getId() {
return getFirstChildText(profileElement, ID_ELEMENT);
}
public boolean isActive() {
return isActiveByDefault() || isActivatedByProperty()
|| isActiveByOS() || isActiveByJDK() || isActiveByFile();
}
public boolean isActiveByDefault() {
Element activation = getFirstChildElement(profileElement, ACTIVATION_ELEMENT);
return Boolean.parseBoolean(getFirstChildText(activation, ACTIVE_BY_DEFAULT_ELEMENT));
}
public boolean isActiveByOS() {
final Element activation = getFirstChildElement(profileElement, ACTIVATION_ELEMENT);
if (activation == null) {
return false;
}
final Element osActivation = getFirstChildElement(activation, OS);
if (osActivation == null) {
return false;
}
final String actualOS = System.getProperty("os.name");
final String expectedOSName = getFirstChildText(osActivation, NAME);
if (expectedOSName != null && !actualOS.equals(expectedOSName.trim())) {
// os name is specified but doesn't match
return false;
}
final String expectedOSFamily = getFirstChildText(osActivation, FAMILY);
if (expectedOSFamily != null && !actualOS.contains(expectedOSFamily.trim())) {
// os family is specified but doesn't match
return false;
}
final String expectedOSArch = getFirstChildText(osActivation, ARCH);
if (expectedOSArch != null && !System.getProperty("os.arch").equals(expectedOSArch.trim())) {
// os arch is specified but doesn't match
return false;
}
final String expectedOSVersion = getFirstChildText(osActivation, VERSION);
if (expectedOSVersion != null && !System.getProperty("os.version").equals(expectedOSVersion.trim())) {
// os version is specified but doesn't match
return false;
}
// reaching here implies that either no OS match rules were specified or
// all of the OS rules that were specified were matched. So we just check to see
// if any rules were specified at all, in which case, we consider the profile to be activated
// by the OS element
return (expectedOSName != null || expectedOSFamily != null || expectedOSArch != null || expectedOSVersion != null);
}
public boolean isActiveByJDK() {
final Element activation = getFirstChildElement(profileElement, ACTIVATION_ELEMENT);
if (activation == null) {
return false;
}
final String expectedJDKRange = getFirstChildText(activation, JDK);
if (expectedJDKRange == null) {
return false;
}
final boolean negate = expectedJDKRange.trim().startsWith("!");
final String nonNegatedRange = negate ? expectedJDKRange.substring(1).trim() : expectedJDKRange.trim();
final boolean javaVersionInRange = MavenVersionRangeParser.currentJavaVersionInRange(nonNegatedRange);
return javaVersionInRange ^ negate;
}
public boolean isActiveByFile() {
final Element activation = getFirstChildElement(profileElement, ACTIVATION_ELEMENT);
if (activation == null) {
return false;
}
final Element fileActivation = getFirstChildElement(activation, FILE);
if (fileActivation == null) {
return false;
}
final String expectedMissing = getFirstChildText(fileActivation, MISSING);
if (expectedMissing != null && new File(expectedMissing.trim()).exists()) {
// the file was specified and expected to be missing, but it exists
return false;
}
final String expectedExists = getFirstChildText(fileActivation, EXISTS);
if (expectedExists != null && !(new File(expectedExists.trim()).exists())) {
// the file was specified and expected to be existing, but it doesn't
return false;
}
// reaching here implies that either no file match rules were specified or
// all of the file rules that were specified were matched. So we just check to see
// if any rules were specified at all, in which case, we consider the profile to be activated
// by the file element
return (expectedMissing != null || expectedExists != null);
}
public boolean isActivatedByProperty() {
Element activation = getFirstChildElement(profileElement, ACTIVATION_ELEMENT);
Element propertyActivation = getFirstChildElement(activation, PROPERTY);
String propertyName = getFirstChildText(propertyActivation, NAME);
if (propertyName == null || "".equals(propertyName)) {
return false;
}
boolean negate = propertyName.charAt(0) == '!';
if (negate) {
propertyName = propertyName.substring(1);
}
if ("".equals(propertyName)) {
return false;
}
String propertyValue = getFirstChildText(propertyActivation, VALUE);
final boolean matched;
if (propertyValue == null || "".equals(propertyValue)) {
matched = PomReader.this.properties.containsKey(propertyName);
} else {
matched = propertyValue.equals(PomReader.this.properties.get(propertyName));
}
return matched ^ negate;
}
public List<PomDependencyData> getDependencies() {
return PomReader.this.getDependencies(profileElement);
}
public List<PomDependencyMgt> getDependencyMgt() {
return PomReader.this.getDependencyMgt(profileElement);
}
public List<PomPluginElement> getPlugins() {
return PomReader.this.getPlugins(profileElement);
}
public Map<String, String> getProfileProperties() {
return PomReader.getProperties(profileElement);
}
}
/**
* @return the content of the properties tag into the pom.
*/
public Map<String, String> getPomProperties() {
return new HashMap<>(getProperties(projectElement));
}
private String replaceProps(String val) {
if (val == null) {
return null;
} else {
return IvyPatternHelper.substituteVariables(val, properties).trim();
}
}
private static String getTextContent(Element element) {
StringBuilder result = new StringBuilder();
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
switch (child.getNodeType()) {
case Node.CDATA_SECTION_NODE:
case Node.TEXT_NODE:
result.append(child.getNodeValue());
break;
default:
break;
}
}
return result.toString();
}
private static String getFirstChildText(Element parentElem, String name) {
Element node = getFirstChildElement(parentElem, name);
if (node != null) {
return getTextContent(node);
} else {
return null;
}
}
private static Element getFirstChildElement(Element parentElem, String name) {
if (parentElem == null) {
return null;
}
NodeList childs = parentElem.getChildNodes();
for (int i = 0; i < childs.getLength(); i++) {
Node node = childs.item(i);
if (node instanceof Element && name.equals(node.getNodeName())) {
return (Element) node;
}
}
return null;
}
private static List<Element> getAllChilds(Element parent) {
List<Element> r = new LinkedList<>();
if (parent != null) {
NodeList childs = parent.getChildNodes();
for (int i = 0; i < childs.getLength(); i++) {
Node node = childs.item(i);
if (node instanceof Element) {
r.add((Element) node);
}
}
}
return r;
}
private static final class AddDTDFilterInputStream extends FilterInputStream {
private static final int MARK = 10000;
private static final String DOCTYPE = "<!DOCTYPE project SYSTEM \"m2-entities.ent\">\n";
private int count;
private byte[] prefix = DOCTYPE.getBytes();
private AddDTDFilterInputStream(InputStream in) throws IOException {
super(new BufferedInputStream(in));
this.in.mark(MARK);
// TODO: we should really find a better solution for this...
// maybe we could use a FilterReader instead of a FilterInputStream?
int byte1 = this.in.read();
int byte2 = this.in.read();
int byte3 = this.in.read();
if (byte1 == 239 && byte2 == 187 && byte3 == 191) {
// skip the UTF-8 BOM
this.in.mark(MARK);
} else {
this.in.reset();
}
int bytesToSkip = 0;
LineNumberReader reader = new LineNumberReader(new InputStreamReader(this.in, StandardCharsets.UTF_8),
100);
String firstLine = reader.readLine();
if (firstLine != null) {
String trimmed = firstLine.trim();
if (trimmed.startsWith("<?xml ")) {
int endIndex = trimmed.indexOf("?>");
String xmlDecl = trimmed.substring(0, endIndex + 2);
prefix = (xmlDecl + "\n" + DOCTYPE).getBytes();
bytesToSkip = xmlDecl.getBytes().length;
}
} else {
prefix = new byte[0];
}
this.in.reset();
for (int i = 0; i < bytesToSkip; i++) {
this.in.read();
}
}
@Override
public int read() throws IOException {
if (count < prefix.length) {
return prefix[count++];
}
return super.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || off > b.length || len < 0 || (off + len) > b.length
|| (off + len) < 0) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int nbrBytesCopied = 0;
if (count < prefix.length) {
int nbrBytesFromPrefix = Math.min(prefix.length - count, len);
System.arraycopy(prefix, count, b, off, nbrBytesFromPrefix);
nbrBytesCopied = nbrBytesFromPrefix;
}
if (nbrBytesCopied < len) {
nbrBytesCopied += in.read(b, off + nbrBytesCopied, len - nbrBytesCopied);
}
count += nbrBytesCopied;
return nbrBytesCopied;
}
}
}