/*******************************************************************************
* 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
*
* http://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.ofbiz.base.util.string;
import java.beans.FeatureDescriptor;
import java.util.Iterator;
import java.util.List;
import javax.el.CompositeELResolver;
import javax.el.ELContext;
import javax.el.ELResolver;
import javax.el.PropertyNotWritableException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import javolution.util.FastList;
import org.apache.xerces.dom.NodeImpl;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.cache.UtilCache;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Defines property resolution behavior on Nodes. This resolver handles base objects that implement
* org.w3c.dom.Node or org.apache.xerces.dom.NodeImpl. It accepts a String as a property and compiles
* that String into an XPathExpression. The resulting value is the evaluation of the XPathExpression
* in the context of the base Node. This resolver is currently only available in read-only mode, which
* means that isReadOnly will always return true and {@link #setValue(ELContext, Object, Object, Object)}
* will always throw PropertyNotWritableException. ELResolvers are combined together using {@link CompositeELResolver}
* s, to define rich semantics for evaluating an expression. See the javadocs for {@link ELResolver}
* for details.
*/
public class NodeELResolver extends ELResolver {
private final XPath xpath;
private final UtilCache<String, XPathExpression> exprCache = UtilCache.createUtilCache("nodeElResolver.ExpressionCache");
private static final String module = NodeELResolver.class.getName();
/**
* Creates a new read-only NodeELResolver.
*/
public NodeELResolver() {
XPathFactory factory = XPathFactory.newInstance();
xpath = factory.newXPath();
}
@Override
public Class<?> getCommonPropertyType(ELContext context, Object base) {
return isResolvable(base) ? String.class : null;
}
@Override
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
return null;
}
@Override
public Class<?> getType(ELContext context, Object base, Object property) {
if (context == null) {
throw new NullPointerException("context is null");
}
Class<?> result = null;
if (isResolvable(base)) {
result = Node.class;
context.setPropertyResolved(true);
}
return result;
}
@Override
public Object getValue(ELContext context, Object base, Object property) {
if (context == null) {
throw new NullPointerException("context is null");
}
Object result = null;
if (isResolvable(base)) {
try {
Node node = (Node) base;
String propertyString = (String) property;
XPathExpression expr = getXPathExpressionInstance(propertyString);
NodeList nodeList = (NodeList) expr.evaluate(node, XPathConstants.NODESET);
if (nodeList.getLength() == 0) {
return null;
} else if (nodeList.getLength() == 1) {
result = nodeList.item(0);
} else {
List<Node> newList = FastList.newInstance();
for (int i = 0; i < nodeList.getLength(); i++) {
newList.add(nodeList.item(i));
}
result = newList;
}
context.setPropertyResolved(true);
} catch (XPathExpressionException e) {
Debug.logError("An error occurred during XPath expression evaluation, error was: " + e, module);
}
}
return result;
}
@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
if (context == null) {
throw new NullPointerException("context is null");
}
if (isResolvable(base)) {
context.setPropertyResolved(true);
}
return true;
}
@Override
public void setValue(ELContext context, Object base, Object property, Object value) {
if (context == null) {
throw new NullPointerException("context is null");
}
if (isResolvable(base)) {
throw new PropertyNotWritableException("resolver is read-only");
}
}
private final boolean isResolvable(Object base) {
return base != null && (base instanceof Node || base instanceof NodeImpl);
}
private XPathExpression getXPathExpressionInstance(String xPathString) {
XPathExpression xpe = exprCache.get(xPathString);
if (xpe == null) {
synchronized (exprCache) {
xpe = exprCache.get(xPathString);
if (xpe == null) {
try {
xpe = xpath.compile(xPathString);
exprCache.put(xPathString, xpe);
} catch (XPathExpressionException e) {
Debug.logError("An error occurred during XPath expression compilation, error was: " + e, module);
}
}
}
}
return xpe;
}
}