/*
* Copyright 2017 Graz University of Technology EAAF-Core Components has been developed in a
* cooperation between EGIZ, A-SIT Plus, A-SIT, and Graz University of Technology.
*
* Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European
* Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work except in
* compliance with the Licence. You may obtain a copy of the Licence at:
* https://joinup.ec.europa.eu/news/understanding-eupl-v12
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence
* is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the Licence for the specific language governing permissions and limitations under
* the Licence.
*
* This product combines work with different licenses. See the "NOTICE" text file for details on the
* various modules and licenses. The "NOTICE" text file is part of the distribution. Any derivative
* works that you distribute must include a readable copy of the "NOTICE" text file.
*/
package at.gv.egiz.eaaf.core.impl.utils;
import java.util.List;
import java.util.Map;
import at.gv.egiz.eaaf.core.api.data.XMLNamespaceConstants;
import at.gv.egiz.eaaf.core.exceptions.XPathException;
import org.jaxen.JaxenException;
import org.jaxen.NamespaceContext;
import org.jaxen.Navigator;
import org.jaxen.SimpleNamespaceContext;
import org.jaxen.dom.DOMXPath;
import org.jaxen.dom.DocumentNavigator;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.traversal.NodeIterator;
/**
* Utility methods to evaluate XPath expressions on DOM nodes.
*
* @author Patrick Peck
* @version $Id$
*/
public class XPathUtils {
/**
* The XPath expression selecting all nodes under a given root (including the root node itself).
*/
public static final String ALL_NODES_XPATH = "(.//. | .//@* | .//namespace::*)";
/** The DocumentNavigator
to use for navigating the document. */
private static Navigator documentNavigator = DocumentNavigator.getInstance();
/** The default namespace prefix to namespace URI mappings. */
private static NamespaceContext NS_CONTEXT;
static {
final SimpleNamespaceContext ctx = new SimpleNamespaceContext();
ctx.addNamespace(XMLNamespaceConstants.MOA_PREFIX, XMLNamespaceConstants.MOA_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.MOA_CONFIG_PREFIX,
XMLNamespaceConstants.MOA_CONFIG_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.MOA_ID_CONFIG_PREFIX,
XMLNamespaceConstants.MOA_ID_CONFIG_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.SL10_PREFIX, XMLNamespaceConstants.SL10_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.SL11_PREFIX, XMLNamespaceConstants.SL11_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.SL12_PREFIX, XMLNamespaceConstants.SL12_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.ECDSA_PREFIX, XMLNamespaceConstants.ECDSA_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.PD_PREFIX, XMLNamespaceConstants.PD_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.SAML_PREFIX, XMLNamespaceConstants.SAML_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.SAMLP_PREFIX, XMLNamespaceConstants.SAMLP_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.DSIG_PREFIX, XMLNamespaceConstants.DSIG_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.XSLT_PREFIX, XMLNamespaceConstants.XSLT_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.XSI_PREFIX, XMLNamespaceConstants.XSI_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.DSIG_FILTER2_PREFIX,
XMLNamespaceConstants.DSIG_FILTER2_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.DSIG_EC_PREFIX, XMLNamespaceConstants.DSIG_EC_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.MD_PREFIX, XMLNamespaceConstants.MD_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.MDP_PREFIX, XMLNamespaceConstants.MDP_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.MVV_PREFIX, XMLNamespaceConstants.MVV_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.STB_PREFIX, XMLNamespaceConstants.STB_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.WRR_PREFIX, XMLNamespaceConstants.WRR_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.STORK_PREFIX, XMLNamespaceConstants.STORK_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.STORKP_PREFIX, XMLNamespaceConstants.STORKP_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.SAML2_PREFIX, XMLNamespaceConstants.SAML2_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.SAML2P_PREFIX, XMLNamespaceConstants.SAML2P_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.XENC_PREFIX, XMLNamespaceConstants.XENC_NS_URI);
ctx.addNamespace(XMLNamespaceConstants.XADES_1_1_1_NS_PREFIX,
XMLNamespaceConstants.XADES_1_1_1_NS_URI);
NS_CONTEXT = ctx;
}
/**
* Return a NodeIterator
over the nodes matching the XPath expression.
*
*
* All namespace URIs and prefixes declared in the Constants
interface are used for
* resolving namespaces.
*
NodeIterator
over the nodes matching the XPath expression.
*
* @param contextNode The root node from which to evaluate the XPath expression.
* @param namespaceElement An element from which to build the namespace mapping for evaluating the
* XPath expression
* @param exp The XPath expression to evaluate.
* @return An iterator over the resulting nodes.
* @throws XPathException An error occurred evaluating the XPath expression.
*/
public static NodeIterator selectNodeIterator(final Node contextNode,
final Element namespaceElement, final String exp) throws XPathException {
try {
final SimpleNamespaceContext ctx = new SimpleNamespaceContext();
ctx.addElementNamespaces(documentNavigator, namespaceElement);
return selectNodeIterator(contextNode, ctx, exp);
} catch (final JaxenException e) {
throw new XPathException("XPath operation FAILED. Reason: " + e.getMessage(), e);
}
}
/**
* Return a NodeIterator
over the nodes matching the XPath expression.
*
* @param contextNode The root node from which to evaluate the XPath expression.
* @param namespaceMapping A namespace prefix to namespace URI mapping (String
to
* String
) for evaluating the XPath expression.
* @param exp The XPath expression to evaluate.
* @return An iterator over the resulting nodes.
* @throws XPathException An error occurred evaluating the XPath expression.
*/
public static NodeIterator selectNodeIterator(final Node contextNode, final Map namespaceMapping,
final String exp) throws XPathException {
final SimpleNamespaceContext ctx = new SimpleNamespaceContext(namespaceMapping);
return selectNodeIterator(contextNode, ctx, exp);
}
/**
* Return a NodeIterator
over the nodes matching the XPath expression.
*
* @param contextNode The root node from which to evaluate the XPath expression.
* @param nsContext The NamespaceContext
for resolving namespace prefixes to
* namespace URIs for evaluating the XPath expression.
* @param exp The XPath expression to evaluate.
* @return An iterator over the resulting nodes.
* @throws XPathException An error occurred evaluating the XPath expression.
*/
private static NodeIterator selectNodeIterator(final Node contextNode,
final NamespaceContext nsContext, final String exp) throws XPathException {
try {
final DOMXPath xpath = new DOMXPath(exp);
List nodes;
xpath.setNamespaceContext(nsContext);
nodes = xpath.selectNodes(contextNode);
return new NodeIteratorAdapter(nodes.listIterator());
} catch (final JaxenException e) {
throw new XPathException("XPath operation FAILED. Reason: " + e.getMessage(), e);
}
}
/**
* Return a NodeList
of all the nodes matching the XPath expression.
*
* All namespace URIs and prefixes declared in the Constants
interface are used for
* resolving namespaces.
*
NodeList
containing the matching nodes.
* @throws XPathException An error occurred evaluating the XPath expression.
*/
public static NodeList selectNodeList(final Node contextNode, final String exp)
throws XPathException {
return selectNodeList(contextNode, NS_CONTEXT, exp);
}
/**
* Return a NodeList
of all the nodes matching the XPath expression.
*
* @param contextNode The root node from which to evaluate the XPath expression.
* @param namespaceElement An element from which to build the namespace mapping for evaluating the
* XPath expression
* @param exp The XPath expression to evaluate.
* @return A NodeList
containing the matching nodes.
* @throws XPathException An error occurred evaluating the XPath expression.
*/
public static NodeList selectNodeList(final Node contextNode, final Element namespaceElement,
final String exp) throws XPathException {
try {
final SimpleNamespaceContext ctx = new SimpleNamespaceContext();
ctx.addElementNamespaces(documentNavigator, namespaceElement);
return selectNodeList(contextNode, ctx, exp);
} catch (final JaxenException e) {
throw new XPathException("XPath operation FAILED. Reason: " + e.getMessage(), e);
}
}
/**
* Return a NodeList
of all the nodes matching the XPath expression.
*
* @param contextNode The root node from which to evaluate the XPath expression.
* @param namespaceMapping A namespace prefix to namespace URI mapping (String
to
* String
) for evaluating the XPath expression.
* @param exp The XPath expression to evaluate.
* @return A NodeList
containing the matching nodes.
* @throws XPathException An error occurred evaluating the XPath expression.
*/
public static NodeList selectNodeList(final Node contextNode, final Map namespaceMapping,
final String exp) throws XPathException {
final SimpleNamespaceContext ctx = new SimpleNamespaceContext(namespaceMapping);
return selectNodeList(contextNode, ctx, exp);
}
/**
* Return a NodeList
of all the nodes matching the XPath expression.
*
* @param contextNode The root node from which to evaluate the XPath expression.
* @param nsContext The NamespaceContext
for resolving namespace prefixes to
* namespace URIs for evaluating the XPath expression.
* @param exp The XPath expression to evaluate.
* @return A NodeList
containing the matching nodes.
* @throws XPathException An error occurred evaluating the XPath expression.
*/
private static NodeList selectNodeList(final Node contextNode, final NamespaceContext nsContext,
final String exp) throws XPathException {
try {
final DOMXPath xpath = new DOMXPath(exp);
List nodes;
xpath.setNamespaceContext(nsContext);
nodes = xpath.selectNodes(contextNode);
return new NodeListAdapter(nodes);
} catch (final JaxenException e) {
throw new XPathException("XPath operation FAILED. Reason: " + e.getMessage(), e);
}
}
/**
* Select the first node matching an XPath expression.
*
* All namespace URIs and prefixes declared in the Constants
interface are used for
* resolving namespaces.
*
null
, if no node
* matched.
* @throws XPathException An error occurred evaluating the XPath expression.
*/
public static Node selectSingleNode(final Node contextNode, final String exp)
throws XPathException {
return selectSingleNode(contextNode, NS_CONTEXT, exp);
}
/**
* Select the first node matching an XPath expression.
*
* @param contextNode The root node from which to evaluate the XPath expression.
* @param namespaceElement An element from which to build the namespace mapping for evaluating the
* XPath expression
* @param exp The XPath expression to evaluate.
* @return Node The first node matching the XPath expression, or null
, if no node
* matched.
* @throws XPathException An error occurred evaluating the XPath expression.
*/
public static Node selectSingleNode(final Node contextNode, final Element namespaceElement,
final String exp) throws XPathException {
try {
final SimpleNamespaceContext ctx = new SimpleNamespaceContext();
ctx.addElementNamespaces(documentNavigator, namespaceElement);
return selectSingleNode(contextNode, ctx, exp);
} catch (final JaxenException e) {
throw new XPathException("XPath operation FAILED. Reason: " + e.getMessage(), e);
}
}
/**
* Select the first node matching an XPath expression.
*
* @param contextNode The root node from which to evaluate the XPath expression.
* @param namespaceMapping A namespace prefix to namespace URI mapping (String
to
* String
) for evaluating the XPath expression.
* @param exp The XPath expression to evaluate.
* @return Node The first node matching the XPath expression, or null
, if no node
* matched.
* @throws XPathException An error occurred evaluating the XPath expression.
*/
public static Node selectSingleNode(final Node contextNode, final Map namespaceMapping,
final String exp) throws XPathException {
final SimpleNamespaceContext ctx = new SimpleNamespaceContext(namespaceMapping);
return selectSingleNode(contextNode, ctx, exp);
}
/**
* Select the first node matching an XPath expression.
*
* @param contextNode The root node from which to evaluate the XPath expression.
* @param nsContext The NamespaceContext
for resolving namespace prefixes to
* namespace URIs for evaluating the XPath expression.
* @param exp The XPath expression to evaluate.
* @return Node The first node matching the XPath expression, or null
, if no node
* matched.
* @throws XPathException An error occurred evaluating the XPath expression.
*/
public static Node selectSingleNode(final Node contextNode, final NamespaceContext nsContext,
final String exp) throws XPathException {
try {
final DOMXPath xpath = new DOMXPath(exp);
xpath.setNamespaceContext(nsContext);
return (Node) xpath.selectSingleNode(contextNode);
} catch (final JaxenException e) {
throw new XPathException("XPath operation FAILED. Reason: " + e.getMessage(), e);
}
}
/**
* Return the value of a DOM element whose location is given by an XPath expression.
*
* @param root The root element from which to evaluate the XPath.
* @param xpath The XPath expression pointing to the element whose value to return.
* @param def The default value to return, if no element can be found using the given
* xpath
.
* @return The element value, if it can be located using the xpath
. Otherwise,
* def
is returned.
*/
public static String getElementValue(final Element root, final String xpath, final String def) {
final Element elem = (Element) XPathUtils.selectSingleNode(root, xpath);
return elem != null ? DomUtils.getText(elem) : def;
}
/**
* Return the value of a DOM attribute whose location is given by an XPath expression.
*
* @param root The root element from which to evaluate the XPath.
* @param xpath The XPath expression pointing to the attribute whose value to return.
* @param def The default value to return, if no attribute can be found using the given
* xpath
.
* @return The element value, if it can be located using the xpath
. Otherwise,
* def
is returned.
*/
public static String getAttributeValue(final Element root, final String xpath, final String def) {
final Attr attr = (Attr) XPathUtils.selectSingleNode(root, xpath);
return attr != null ? attr.getValue() : def;
}
/**
* Returns the namespace prefix used within XPathUtils
for referring to the namespace
* of the specified (Security Layer command) element.
*
* This namespace prefix can be used in various XPath expression evaluation methods within
* XPathUtils
without explicitely binding it to the particular namespace.
*
XPathUtils
for referring to the namespace
* of the specified (Security Layer command) element.
*
* throws XpathException If the specified element has a namespace other than the ones
* known by this implementation as valid Security Layer namespaces (cf.
* @link Constants#SL10_NS_URI, @link Constants#SL11_NS_URI, @link Constants#SL12_NS_URI).
*/
public static String getSlPrefix(final Element contextElement) throws XPathException {
final String sLNamespace = contextElement.getNamespaceURI();
String slPrefix = null;
if (sLNamespace.equals(XMLNamespaceConstants.SL10_NS_URI)) {
slPrefix = XMLNamespaceConstants.SL10_PREFIX;
} else if (sLNamespace.equals(XMLNamespaceConstants.SL12_NS_URI)) {
slPrefix = XMLNamespaceConstants.SL12_PREFIX;
} else if (sLNamespace.equals(XMLNamespaceConstants.SL11_NS_URI)) {
slPrefix = XMLNamespaceConstants.SL11_PREFIX;
} else {
throw new XPathException("XPath operation FAILED. Reason: ");
}
return slPrefix;
}
/**
* Return the SecurityLayer namespace prefix of the context element. If the context element is not
* the element that lies within the SecurityLayer namespace. The Securitylayer namespace is
* derived from the xmlns:sl10
, sl11
or sl
attribute of the
* context element.
** The returned prefix is needed for evaluating XPATH expressions. *
* @param contextElement The element to get a prefix for the Securitylayer namespace, that is used * within the corresponding document. * * @return The stringsl10
, sl11
or sl
, depending on the
* SecurityLayer namespace of the contextElement.
*
* throws XPathException If no (vlalid) SecurityLayer namespace prefix or namespace is
* defined.
*/
public static String getSlPrefixFromNoRoot(final Element contextElement) throws XPathException {
String slPrefix = checkSLnsDeclaration(contextElement, XMLNamespaceConstants.SL10_PREFIX,
XMLNamespaceConstants.SL10_NS_URI);
if (slPrefix == null) {
slPrefix = checkSLnsDeclaration(contextElement, XMLNamespaceConstants.SL11_PREFIX,
XMLNamespaceConstants.SL11_NS_URI);
}
if (slPrefix == null) {
slPrefix = checkSLnsDeclaration(contextElement, XMLNamespaceConstants.SL12_PREFIX,
XMLNamespaceConstants.SL12_NS_URI);
}
return slPrefix;
}
/**
* Checks if the context element has an attribute xmlns:slPrefix
and if the prefix of
* that attribute corresponds with a valid SecurityLayer namespace.
*
* @param contextElement The element to be checked.
* @param slPrefix The prefix which should be checked. Must be a valid SecurityLayer namespace
* prefix.
* @param slNameSpace The SecurityLayer namespace that corresponds to the specified prefix.
*
* @return The valid SecurityLayer prefix or null
if this prefix is not used.
* @throws XPathException In case of an error
*/
private static String checkSLnsDeclaration(final Element contextElement, final String slPrefix,
final String slNameSpace) throws XPathException {
final String nsAtt = "xmlns:" + slPrefix;
final String nameSpace = contextElement.getAttribute(nsAtt);
if (nameSpace == "") {
return null;
} else {
// check if namespace is correct
if (nameSpace.equals(slNameSpace)) {
return slPrefix;
} else {
throw new XPathException("Unknown Namespace declaration");
}
}
}
}