package org.fcrepo.utilities.xml;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.xpath.*;
/**
*
*/
public class XPathSelectorImpl implements XPathSelector {
private static final Logger log = LoggerFactory.getLogger(DOM.class);
/**
* Importatnt: All access to the xpathCompiler should be synchronized on it
* since it is not thread safe!
*/
private static final XPath xpathCompiler =
XPathFactory.newInstance().newXPath();
private final LRUCache<String, XPathExpression> cache;
private final NamespaceContext nsContext;
public XPathSelectorImpl(NamespaceContext nsContext, int cacheSize) {
this.nsContext = nsContext;
this.cache = new LRUCache<String, XPathExpression>(cacheSize);
}
@Override
public Integer selectInteger(Node node, String xpath, Integer defaultValue) {
String strVal = selectString(node, xpath);
if (strVal == null || strVal.isEmpty()) {
return defaultValue;
}
return Integer.valueOf(strVal);
}
@Override
public Integer selectInteger(Node node, String xpath) {
return selectInteger(node, xpath, null);
}
@Override
public Double selectDouble(Node node, String xpath, Double defaultValue) {
Double d = (Double) selectObject(node, xpath, XPathConstants.NUMBER);
if (d == null || d.equals(Double.NaN)) {
d = defaultValue;
}
return d;
}
@Override
public Double selectDouble(Node node, String xpath) {
return selectDouble(node, xpath, null);
}
@Override
public Boolean selectBoolean(Node node, String xpath, Boolean defaultValue) {
if (defaultValue == null || Boolean.TRUE.equals(defaultValue)) {
// Using QName.BOOLEAN will always return false if it is not found
// therefore we must try and look it up as a string
String tmp = selectString(node, xpath, null);
if (tmp == null) {
return defaultValue;
}
return Boolean.parseBoolean(tmp);
} else {
// The defaultValue is false so we can always just return what
// we take from the XPath expression
return (Boolean) selectObject(node, xpath, XPathConstants.BOOLEAN);
}
}
@Override
public Boolean selectBoolean(Node node, String xpath) {
return selectBoolean(node, xpath, false);
}
@Override
public String selectString(Node node, String xpath, String defaultValue) {
if ("".equals(defaultValue)) {
// By default the XPath engine will return an empty string
// if it is unable to find the requested path
return (String) selectObject(node, xpath, XPathConstants.STRING);
}
Node n = selectNode(node, xpath);
if (n == null) {
return defaultValue;
}
// FIXME: Can we avoid running the xpath twice?
// The local expression cache helps, but anyway...
return (String) selectObject(node, xpath, XPathConstants.STRING);
}
@Override
public String selectString(Node node, String xpath) {
return selectString(node, xpath, "");
}
@Override
public NodeList selectNodeList(Node dom, String xpath) {
return (NodeList) selectObject(dom, xpath, XPathConstants.NODESET);
}
@Override
public Node selectNode(Node dom, String xpath) {
return (Node) selectObject(dom, xpath, XPathConstants.NODE);
}
private Object selectObject(Node dom, String xpath, QName returnType) {
Object retval = null;
try {
// Get the compiled xpath from the cache or compile and
// cache it if we don't have it
XPathExpression exp = cache.get(xpath);
if (exp == null) {
synchronized (xpathCompiler) {
if (nsContext != null) {
xpathCompiler.setNamespaceContext(nsContext);
}
exp = xpathCompiler.compile(xpath);
}
cache.put(xpath, exp);
}
retval = exp.evaluate(dom, returnType);
} catch (NullPointerException e) {
log.debug(String.format(
"NullPointerException when extracting XPath '%s' on " +
"element type %s. Returning null",
xpath, returnType.getLocalPart()), e);
} catch (XPathExpressionException e) {
log.warn(String.format(
"Error in XPath expression '%s' when selecting %s: %s",
xpath, returnType.getLocalPart(), e.getMessage()), e);
}
return retval;
}
void clearCache() {
cache.clear();
}
}