XPathAPI.java revision 905b8e215b24a1b5d547692cef530c0d2ab545c9
/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2015-2016 ForgeRock AS.
*/
package com.sun.identity.shared.xml;
import java.util.HashMap;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathException;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathConstants;
import org.forgerock.openam.utils.PerThreadCache;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.sun.identity.shared.Constants;
import com.sun.identity.shared.configuration.SystemPropertiesManager;
/**
* <code>XPathAPI</code> class provides the convenience function for XPath API
* that is a subset of Xalan XPathAPI but uses JAXP internally.
*/
public class XPathAPI {
static private final int CACHE_SIZE =
SystemPropertiesManager.getAsInt(Constants.XPATHFACTORY_CACHE_SIZE, 500);
static private final PerThreadCache<XPathFactory, RuntimeException> xpathFactoryCache =
new PerThreadCache<XPathFactory, RuntimeException>(CACHE_SIZE) {
@Override
protected XPathFactory initialValue() {
return XPathFactory.newInstance();
}
};
private XPathAPI() {
}
/**
* Use an XPath string to select a single node.
* Namespace prefix is resolved using the document node.
*
* @param doc The node to start searching from with the embedded context.
* @param str XPath string.
* @return The first node found that matches the XPath, or null.
*
* @throws XPathException
*/
public static Node selectSingleNode(Node doc, String str) throws XPathException {
return selectSingleNode(doc, str, doc);
}
/**
* Use an XPath string to select a single node using a provided element namespace.
*
* @param doc The node to start searching from.
* @param str XPath string.
* @param nsNode Node where namespace prefix in XPath is resolved from.
* @return The first node found that matches the XPath, or null.
*
* @throws XPathException
*/
public static Node selectSingleNode(Node doc, String str, Node nsNode) throws XPathException {
SimpleNamespaceContext nsctx = new SimpleNamespaceContext(nsNode);
return selectSingleNode(doc, str, nsctx);
}
/**
* Use an XPath string to select a single node using a passed in namespace context.
*
* @param doc The node to start searching from.
* @param str XPath string.
* @param nsctx Namespace where the prefix in XPath is resolved from.
* @return The first node found that matches the XPath, or null.
*
* @throws XPathException
*/
public static Node selectSingleNode(Node doc, String str, NamespaceContext nsctx) throws XPathException {
NodeList nl = selectNodeList(doc, str, nsctx);
if (nl.getLength()==0) {
return null;
}
return nl.item(0);
}
/**
* Use an XPath string to select a nodelist.
* Namespace prefix is resolved using the document node.
*
* @param doc The node to start searching from.
* @param str XPath string.
* @return a NodeList of the matched result.
*
* @throws XPathException
*/
public static NodeList selectNodeList(Node doc, String str) throws XPathException {
return selectNodeList(doc, str, doc);
}
/**
* Use an XPath string to select a nodelist using a node namespace.
*
* @param doc The node to start searching from.
* @param str XPath string.
* @param nsNode Node where namespace prefix in XPath is resolved from.
* @return a NodeList of the matched result.
*
* @throws XPathException
*/
public static NodeList selectNodeList(Node doc, String str, Node nsNode) throws XPathException {
SimpleNamespaceContext nsctx = new SimpleNamespaceContext(nsNode);
return selectNodeList(doc, str, nsctx);
}
/**
* Use an XPath string to select a nodelist
* Namespace prefix is resolved using the the specified context.
*
* @param doc The node to start searching from.
* @param str XPath string.
* @param nsctx Namespace where the prefix in XPath is resolved from.
* @return a NodeList.
*
* @throws XPathException
*/
public static NodeList selectNodeList(Node doc, String str, NamespaceContext nsctx) throws XPathException {
XPathFactory xpf = xpathFactoryCache.getInstanceForCurrentThread();
XPath xpath = xpf.newXPath();
xpath.setNamespaceContext(nsctx);
XPathExpression expr = xpath.compile(str);
return (NodeList) expr.evaluate(doc,XPathConstants.NODESET);
}
/**
* SimpleNamespaceContext implements just enough NamespaceContext functionality
* for XPath.
*/
static class SimpleNamespaceContext implements NamespaceContext {
private Map<String, String> pfxmap = new HashMap<String, String>();
private Map<String, List<String>> uri2pfxmap = new HashMap<String, List<String>>();
public SimpleNamespaceContext(Node node) {
pfxmap.put(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI);
pfxmap.put(XMLConstants.XMLNS_ATTRIBUTE, XMLConstants.XMLNS_ATTRIBUTE_NS_URI);
addNamespaces(node);
}
public String getNamespaceURI(String prefix) {
if (prefix == null) {
throw new IllegalArgumentException();
} else if (pfxmap.containsKey(prefix)) {
return pfxmap.get(prefix);
}
return "";
}
public String getPrefix(String namespaceUri) {
if (namespaceUri == null) {
throw new IllegalArgumentException();
}
for (Map.Entry<String,String> entry : pfxmap.entrySet()) {
if (entry.getValue().equals(namespaceUri)) {
return entry.getKey();
}
}
return null;
}
public Iterator<String> getPrefixes(String namespaceUri) {
List<String> prefixes;
if (XMLConstants.XML_NS_URI.equals(namespaceUri)) {
prefixes = Collections.singletonList(XMLConstants.XML_NS_PREFIX);
} else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceUri)) {
prefixes = Collections.singletonList(XMLConstants.XMLNS_ATTRIBUTE);
} else {
prefixes = uri2pfxmap.get(namespaceUri);
if (prefixes == null) {
prefixes = Collections.EMPTY_LIST;
}
}
return prefixes.iterator();
}
private void addNamespaces(Node element) {
if (element.getParentNode() != null) {
addNamespaces(element.getParentNode());
}
if (element.getNodeType() == Node.DOCUMENT_NODE) {
element = ((Document)element).getDocumentElement();
}
if (element instanceof Element) {
Element el = (Element)element;
NamedNodeMap nmap = el.getAttributes();
for (int x = 0; x < nmap.getLength(); x++) {
Attr attr = (Attr)nmap.item(x);
if ("xmlns".equals(attr.getPrefix())) {
String name = attr.getLocalName();
String nsUri = attr.getValue();
pfxmap.put(name, nsUri);
List<String> list = uri2pfxmap.get(nsUri);
if (list == null) {
list = new ArrayList<String>();
uri2pfxmap.put(nsUri,list);
}
list.add(name);
}
}
}
}
}
}