/*
|
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
|
* Use is subject to license terms.
|
*
|
* Redistribution and use in source and binary forms, with or without modification, are
|
* permitted provided that the following conditions are met: Redistributions of source code
|
* must retain the above copyright notice, this list of conditions and the following disclaimer.
|
* Redistributions in binary form must reproduce the above copyright notice, this list of
|
* conditions and the following disclaimer in the documentation and/or other materials
|
* provided with the distribution. Neither the name of the Sun Microsystems nor the names of
|
* is contributors may be used to endorse or promote products derived from this software
|
* without specific prior written permission.
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
|
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
* POSSIBILITY OF SUCH DAMAGE.
|
*/
|
|
/*
|
* XPathScriptEngine.java
|
* @author A. Sundararajan
|
*/
|
|
package org.jbpm.pvm.internal.script;
|
|
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayOutputStream;
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.io.InputStreamReader;
|
import java.io.Reader;
|
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Method;
|
import java.lang.reflect.Modifier;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.HashMap;
|
import java.util.Iterator;
|
import java.util.List;
|
import java.util.Map;
|
|
import javax.script.AbstractScriptEngine;
|
import javax.script.Bindings;
|
import javax.script.Compilable;
|
import javax.script.CompiledScript;
|
import javax.script.Invocable;
|
import javax.script.ScriptContext;
|
import javax.script.ScriptEngine;
|
import javax.script.ScriptEngineFactory;
|
import javax.script.ScriptException;
|
import javax.script.SimpleBindings;
|
import javax.xml.XMLConstants;
|
import javax.xml.namespace.NamespaceContext;
|
import javax.xml.namespace.QName;
|
import javax.xml.transform.Result;
|
import javax.xml.transform.Source;
|
import javax.xml.transform.TransformerFactory;
|
import javax.xml.transform.dom.DOMSource;
|
import javax.xml.transform.stream.StreamResult;
|
import javax.xml.xpath.XPath;
|
import javax.xml.xpath.XPathConstants;
|
import javax.xml.xpath.XPathExpression;
|
import javax.xml.xpath.XPathFactory;
|
import javax.xml.xpath.XPathFunction;
|
import javax.xml.xpath.XPathFunctionResolver;
|
import javax.xml.xpath.XPathVariableResolver;
|
|
import org.w3c.dom.Document;
|
import org.xml.sax.InputSource;
|
|
public class XPathScriptEngine extends AbstractScriptEngine implements Compilable {
|
|
// my factory, may be null
|
private ScriptEngineFactory factory;
|
private XPathFactory xpathFactory;
|
|
// special context variables for XPath result type and input
|
public static final String XPATH_RESULT_TYPE = "com.sun.script.xpath.resultType";
|
public static final String XPATH_INPUT_SRC = "com.sun.script.xpath.inputSource";
|
|
// XML namespace prefixes and URIs.
|
public static final String XMLNS_COLON = "xmlns:";
|
public static final String XPATH_CONTEXT_PREFIX = "context";
|
public static final String XPATH_CONTEXT_URI = "http://www.sun.com/java/jsr223/xpath/context";
|
|
private Document objectData;
|
|
public XPathScriptEngine() {
|
xpathFactory = XPathFactory.newInstance();
|
}
|
|
// my implementation for CompiledScript
|
private class XPathCompiledScript extends CompiledScript {
|
|
private XPathExpression expr;
|
|
XPathCompiledScript(XPathExpression expr) {
|
this.expr = expr;
|
}
|
|
public ScriptEngine getEngine() {
|
return XPathScriptEngine.this;
|
}
|
|
public Object eval(ScriptContext ctx) throws ScriptException {
|
return evalXPath(expr, ctx);
|
}
|
}
|
|
public CompiledScript compile(String script) throws ScriptException {
|
XPathExpression expr = compileXPath(script, context);
|
return new XPathCompiledScript(expr);
|
}
|
|
public CompiledScript compile(Reader reader) throws ScriptException {
|
return compile(readFully(reader));
|
}
|
|
public Object eval(String str, ScriptContext ctx) throws ScriptException {
|
XPathExpression expr = compileXPath(str, ctx);
|
return evalXPath(expr, ctx);
|
}
|
|
public Object eval(Reader reader, ScriptContext ctx) throws ScriptException {
|
return eval(readFully(reader), ctx);
|
}
|
|
public ScriptEngineFactory getFactory() {
|
synchronized (this) {
|
if (factory == null) {
|
factory = new XPathScriptEngineFactory();
|
}
|
}
|
return factory;
|
}
|
|
public Bindings createBindings() {
|
return new SimpleBindings();
|
}
|
|
void setFactory(ScriptEngineFactory factory) {
|
this.factory = factory;
|
}
|
|
// Internals only below this point
|
|
// find a variable of given qname in given context
|
private static Object findVariable(QName qname, ScriptContext ctx) {
|
String name;
|
int scope;
|
|
if (XPATH_CONTEXT_URI.equals(qname.getNamespaceURI())) {
|
name = qname.getLocalPart();
|
synchronized (ctx) {
|
scope = ctx.getAttributesScope(name);
|
if (scope != -1) {
|
return ctx.getAttribute(name, scope);
|
} // else fallthru
|
}
|
}
|
if (qname.getPrefix() == null || "".equals(qname.getPrefix())) {
|
name = qname.getLocalPart();
|
} else {
|
name = qname.getPrefix() + ":" + qname.getLocalPart();
|
}
|
synchronized (ctx) {
|
scope = ctx.getAttributesScope(name);
|
if (scope != -1) {
|
return ctx.getAttribute(name, scope);
|
} // else fallthru
|
}
|
return null;
|
}
|
|
private static void collectNamespaces(Map<String, String> map, Bindings scope) {
|
for (String key : scope.keySet()) {
|
if (key.startsWith(XMLNS_COLON)) {
|
Object uri = scope.get(key);
|
// collect all variables starting with "xmlns:" and
|
// collect the prefix to URI mappings.
|
String prefix = key.substring(XMLNS_COLON.length());
|
if (uri instanceof String) {
|
String tmp = (String) uri;
|
if (tmp.length() != 0) {
|
map.put(prefix, tmp);
|
}
|
}
|
}
|
}
|
}
|
|
private static NamespaceContext makeNamespaceContext(ScriptContext ctx) {
|
// namespace prefix-to-URI mappings
|
final Map<String, String> namespaces = new HashMap<String, String>();
|
for (int scope : ctx.getScopes()) {
|
Bindings bind = ctx.getBindings(scope);
|
if (bind != null) {
|
// TODO: See what needs to be done....
|
// collectNamespaces(namespaces, bind);
|
}
|
}
|
|
// look for mapping for default XML namespace
|
Object def = ctx.getAttribute(XMLConstants.XMLNS_ATTRIBUTE);
|
if (def instanceof String) {
|
namespaces.put(XMLConstants.DEFAULT_NS_PREFIX, (String) def);
|
}
|
|
// standard required mappings by XPath standard
|
namespaces.put(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI);
|
namespaces.put(XMLConstants.XMLNS_ATTRIBUTE, XMLConstants.XMLNS_ATTRIBUTE_NS_URI);
|
|
// add prefix mapping for XPATH_CONTEXT_PREFIX
|
namespaces.put(XPATH_CONTEXT_PREFIX, XPATH_CONTEXT_URI);
|
|
return new NamespaceContext() {
|
|
public String getNamespaceURI(String prefix) {
|
if (prefix == null) {
|
throw new IllegalArgumentException();
|
}
|
String uri = namespaces.get(prefix);
|
if (uri == null) {
|
return XMLConstants.NULL_NS_URI;
|
} else {
|
return uri;
|
}
|
}
|
|
public String getPrefix(String namespaceURI) {
|
if (namespaceURI == null) {
|
throw new IllegalArgumentException();
|
}
|
for (String prefix : namespaces.keySet()) {
|
String uri = namespaces.get(prefix);
|
if (namespaceURI.equals(uri)) {
|
return prefix;
|
}
|
}
|
return null;
|
}
|
|
public Iterator getPrefixes(String namespaceURI) {
|
if (namespaceURI == null) {
|
throw new IllegalArgumentException();
|
}
|
List list = new ArrayList();
|
for (String prefix : namespaces.keySet()) {
|
String uri = namespaces.get(prefix);
|
if (namespaceURI.equals(uri)) {
|
list.add(prefix);
|
}
|
}
|
return Collections.unmodifiableList(list).iterator();
|
}
|
};
|
}
|
|
private static XPathFunction makeXPathFunction(final Constructor ctr, int arity) {
|
if (ctr.getParameterTypes().length != arity) {
|
return null;
|
}
|
return new XPathFunction() {
|
|
public Object evaluate(List args) {
|
try {
|
return ctr.newInstance(args.toArray());
|
} catch (Exception exp) {
|
throw new RuntimeException(exp);
|
}
|
}
|
};
|
}
|
|
private static XPathFunction makeXPathFunction(final Method method, int arity) {
|
int modifiers = method.getModifiers();
|
int numArgs = method.getParameterTypes().length;
|
if (Modifier.isStatic(modifiers) && numArgs == arity) {
|
// static method. expose "as is".
|
return new XPathFunction() {
|
|
public Object evaluate(List args) {
|
try {
|
return method.invoke(null, args.toArray());
|
} catch (Exception exp) {
|
throw new RuntimeException(exp);
|
}
|
}
|
};
|
} else if ((numArgs + 1) == arity) {
|
// instance method. treat the first arg as 'this'
|
return new XPathFunction() {
|
|
public Object evaluate(List args) {
|
List tmp = args.subList(1, args.size());
|
try {
|
return method.invoke(args.get(0), tmp.toArray());
|
} catch (Exception exp) {
|
throw new RuntimeException(exp);
|
}
|
}
|
};
|
} else {
|
return null;
|
}
|
}
|
|
private static XPathFunction makeXPathFunction(final String funcName, final Invocable invocable) {
|
return new XPathFunction() {
|
|
public Object evaluate(List args) {
|
try {
|
return invocable.invokeFunction(funcName, args.toArray());
|
} catch (Exception exp) {
|
throw new RuntimeException(exp);
|
}
|
}
|
};
|
}
|
|
// make a XPathFunction from given object
|
private static XPathFunction makeXPathFunction(QName qname, Object obj, int arity) {
|
if (obj == null) {
|
return null;
|
} else if (obj instanceof XPathFunction) {
|
// already XPathFunction - just return
|
return (XPathFunction) obj;
|
} else if (obj instanceof Method) {
|
// a Method object. wrap as XPathFunction
|
return makeXPathFunction((Method) obj, arity);
|
} else if (obj instanceof Constructor) {
|
// a Constructor object. wrap as XPathFunction
|
return makeXPathFunction((Constructor) obj, arity);
|
} else if (obj instanceof Invocable) {
|
// wrap a script function as XPathFunction. Using this,
|
// scripts from other languages (for eg. JavaScript) can use
|
// this engine and pass script functions as XPathFunction extensions
|
|
return makeXPathFunction(qname.getLocalPart(), (Invocable) obj);
|
} else {
|
// can't map the object as XPathFunction.
|
return null;
|
}
|
}
|
|
// Internals only below this point
|
private XPathExpression compileXPath(String str, final ScriptContext ctx) throws ScriptException {
|
// JSR-223 requirement
|
ctx.setAttribute("context", ctx, ScriptContext.ENGINE_SCOPE);
|
try {
|
XPath xpath = xpathFactory.newXPath();
|
xpath.setXPathVariableResolver(new XPathVariableResolver() {
|
|
public Object resolveVariable(QName qname) {
|
return findVariable(qname, ctx);
|
}
|
});
|
|
xpath.setXPathFunctionResolver(new XPathFunctionResolver() {
|
|
public XPathFunction resolveFunction(QName qname, int arity) {
|
Object obj = findVariable(qname, ctx);
|
return makeXPathFunction(qname, obj, arity);
|
}
|
});
|
xpath.setNamespaceContext(makeNamespaceContext(ctx));
|
// xpath.setNamespaceContext(new BpmnFunctionResolver());
|
// xpath.setXPathFunctionResolver(new BpmnFunctionResolver());
|
int begin = str.indexOf("getObjectData") > -1 ? 14 : 0;
|
if (begin > 0) {
|
String objectDataRef = str.substring(begin + 1, str.indexOf(")") - 1);
|
|
objectData = (Document) ctx.getAttribute(objectDataRef);
|
// ctx.setAttribute(XPATH_INPUT_SRC, objectData , 100);
|
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
Source xmlSource = new DOMSource(objectData);
|
Result outputTarget = new StreamResult(outputStream);
|
TransformerFactory.newInstance().newTransformer().transform(xmlSource, outputTarget);
|
InputStream is = new ByteArrayInputStream(outputStream.toByteArray());
|
ctx.setReader(new InputStreamReader(is));
|
|
str = str.substring(str.indexOf(")") + 1);
|
}
|
XPathExpression xpe = xpath.compile(str);
|
return xpe;
|
} catch (Exception exp) {
|
throw new ScriptException(exp);
|
}
|
}
|
|
private Object getVariable(ScriptContext ctx, String name) {
|
synchronized (ctx) {
|
int scope = ctx.getAttributesScope(name);
|
if (scope != -1) {
|
return ctx.getAttribute(name, scope);
|
}
|
}
|
return null;
|
}
|
private Object evalXPath(XPathExpression expr, final ScriptContext ctx) throws ScriptException {
|
|
try {
|
Object resultType = getVariable(ctx, XPATH_RESULT_TYPE);
|
Object input = getVariable(ctx, XPATH_INPUT_SRC);
|
|
InputSource src;
|
if (input == null) {
|
// no input specified, use context reader
|
src = new InputSource(ctx.getReader());
|
} else {
|
// explicit InputSource specified
|
if (input instanceof InputSource) {
|
src = (InputSource) input;
|
} else if (input instanceof String) {
|
src = new InputSource((String) input);
|
} else if (input instanceof Reader) {
|
src = new InputSource((Reader) input);
|
} else if (input instanceof InputStream) {
|
src = new InputSource((InputStream) input);
|
} else {
|
// some other object input type specified (Node/NodeList)
|
src = null;
|
}
|
}
|
|
resultType = XPathConstants.BOOLEAN;
|
|
if (resultType instanceof QName) {
|
return (src != null) ? expr.evaluate(src, (QName) resultType) : expr.evaluate(input, (QName) resultType);
|
} else {
|
return (src != null) ? expr.evaluate(src) : expr.evaluate(input);
|
}
|
} catch (Exception exp) {
|
throw new ScriptException(exp);
|
}
|
}
|
|
private String readFully(Reader reader) throws ScriptException {
|
char[] arr = new char[8 * 1024]; // 8K at a time
|
StringBuilder buf = new StringBuilder();
|
int numChars;
|
try {
|
while ((numChars = reader.read(arr, 0, arr.length)) > 0) {
|
buf.append(arr, 0, numChars);
|
}
|
} catch (IOException exp) {
|
throw new ScriptException(exp);
|
}
|
return buf.toString();
|
}
|
}
|