blob: 36bf2ea338d1ebd8474497340bbb90c02ad30412 [file] [log] [blame]
/*
* 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.apache.ode.bpel.elang.xquery10.runtime;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.transform.TransformerFactory;
import javax.xml.xpath.XPathConstants;
import javax.xml.xquery.XQConnection;
import javax.xml.xquery.XQConstants;
import javax.xml.xquery.XQDataSource;
import javax.xml.xquery.XQException;
import javax.xml.xquery.XQItem;
import javax.xml.xquery.XQItemType;
import javax.xml.xquery.XQPreparedExpression;
import javax.xml.xquery.XQResultSequence;
import javax.xml.xquery.XQSequence;
import javax.xml.xquery.XQSequenceType;
import javax.xml.xquery.XQStaticContext;
import net.sf.saxon.Configuration;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.Validation;
import net.sf.saxon.trans.DynamicError;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.value.DurationValue;
import net.sf.saxon.value.Value;
import net.sf.saxon.xqj.SaxonXQConnection;
import net.sf.saxon.xqj.SaxonXQDataSource;
import net.sf.saxon.xqj.SaxonXQItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.ode.bpel.common.FaultException;
import org.apache.ode.bpel.elang.xpath20.compiler.Constants;
import org.apache.ode.bpel.elang.xpath20.compiler.WrappedResolverException;
import org.apache.ode.bpel.elang.xpath20.runtime.JaxpFunctionResolver;
import org.apache.ode.bpel.elang.xpath20.runtime.JaxpVariableResolver;
import org.apache.ode.bpel.elang.xquery10.compiler.XQuery10BpelFunctions;
import org.apache.ode.bpel.elang.xquery10.obj.OXQuery10ExpressionBPEL20;
import org.apache.ode.bpel.explang.ConfigurationException;
import org.apache.ode.bpel.explang.EvaluationContext;
import org.apache.ode.bpel.explang.EvaluationException;
import org.apache.ode.bpel.explang.ExpressionLanguageRuntime;
import org.apache.ode.bpel.obj.OExpression;
import org.apache.ode.utils.DOMUtils;
import org.apache.ode.utils.ISO8601DateParser;
import org.apache.ode.utils.NSContext;
import org.apache.ode.utils.xsd.Duration;
import org.apache.ode.utils.xsl.XslTransformHandler;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
/**
* XQuery 1.0 Expression Language run-time subsytem. Saxon implementation.
*/
@SuppressWarnings("deprecation")
public class XQuery10ExpressionRuntime implements ExpressionLanguageRuntime {
static final short NODE_TYPE = 1;
static final short NODESET_TYPE = 2;
static final short STRING_TYPE = 3;
static final short BOOLEAN_TYPE = 4;
static final short NUMBER_TYPE = 5;
/** Class-level logger. */
private static final Logger __log = LoggerFactory.getLogger(XQuery10ExpressionRuntime.class);
/**
* Creates a new XQuery10ExpressionRuntime object.
*/
public XQuery10ExpressionRuntime() {
}
/**
* Initialize XSL Transformer
*
* @param properties properties
*
* @throws ConfigurationException ConfigurationException
*/
public void initialize(Map properties) throws ConfigurationException {
TransformerFactory trsf = new net.sf.saxon.TransformerFactoryImpl();
XslTransformHandler.getInstance().setTransformerFactory(trsf);
}
/**
*
* @see org.apache.ode.bpel.explang.ExpressionLanguageRuntime#evaluateAsString(org.apache.ode.bpel.obj.OExpression,
* org.apache.ode.bpel.explang.EvaluationContext)
*/
public String evaluateAsString(OExpression cexp, EvaluationContext ctx)
throws FaultException, EvaluationException {
return (String) evaluate(cexp, ctx, XPathConstants.STRING);
}
/**
*
* @see org.apache.ode.bpel.explang.ExpressionLanguageRuntime#evaluateAsBoolean(org.apache.ode.bpel.obj.OExpression,
* org.apache.ode.bpel.explang.EvaluationContext)
*/
public boolean evaluateAsBoolean(OExpression cexp, EvaluationContext ctx)
throws FaultException, EvaluationException {
return (Boolean) evaluate(cexp, ctx, XPathConstants.BOOLEAN);
}
/**
* Evaluate expression and return a number
*
* @param cexp cexp
* @param ctx ctx
*
* @return type
*
* @throws FaultException FaultException
* @throws EvaluationException EvaluationException
*/
public Number evaluateAsNumber(OExpression cexp, EvaluationContext ctx)
throws FaultException, EvaluationException {
return (Number) evaluate(cexp, ctx, XPathConstants.NUMBER);
}
/**
*
* @see org.apache.ode.bpel.explang.ExpressionLanguageRuntime#evaluate(org.apache.ode.bpel.obj.OExpression,
* org.apache.ode.bpel.explang.EvaluationContext)
*/
public List evaluate(OExpression cexp, EvaluationContext ctx)
throws FaultException, EvaluationException {
List result;
Object someRes = evaluate(cexp, ctx, XPathConstants.NODESET);
if (someRes instanceof List) {
result = (List) someRes;
if (__log.isDebugEnabled()) {
__log.debug("Returned list of size " + result.size());
}
if ((result.size() == 1) && !(result.get(0) instanceof Node)) {
// Dealing with a Java class
Object simpleType = result.get(0);
// Dates get a separate treatment as we don't want to call toString on them
String textVal;
if (simpleType instanceof Date) {
textVal = ISO8601DateParser.format((Date) simpleType);
} else if (simpleType instanceof DurationValue) {
textVal = ((DurationValue) simpleType).getStringValue();
} else {
textVal = simpleType.toString();
}
// Wrapping in a document
Document d = DOMUtils.newDocument();
// Giving our node a parent just in case it's an LValue expression
Element wrapper = d.createElement("wrapper");
Text text = d.createTextNode(textVal);
wrapper.appendChild(text);
d.appendChild(wrapper);
result = Collections.singletonList(text);
}
} else if (someRes instanceof NodeList) {
NodeList retVal = (NodeList) someRes;
if (__log.isDebugEnabled()) {
__log.debug("Returned node list of size " + retVal.getLength());
}
result = new ArrayList(retVal.getLength());
for (int m = 0; m < retVal.getLength(); ++m) {
Node val = retVal.item(m);
if (val.getNodeType() == Node.DOCUMENT_NODE) {
val = ((Document) val).getDocumentElement();
}
result.add(val);
}
} else {
result = null;
}
return result;
}
/**
* Evaluate expression and return a node
*
* @param cexp cexp
* @param ctx ctx
*
* @return type
*
* @throws FaultException FaultException
* @throws EvaluationException EvaluationException
*/
public Node evaluateNode(OExpression cexp, EvaluationContext ctx)
throws FaultException, EvaluationException {
List retVal = evaluate(cexp, ctx);
if (retVal.size() == 0) {
throw new FaultException(cexp.getOwner().getConstants().getQnSelectionFailure(),
"No results for expression: " + cexp);
}
if (retVal.size() > 1) {
throw new FaultException(cexp.getOwner().getConstants().getQnSelectionFailure(),
"Multiple results for expression: " + cexp);
}
return (Node) retVal.get(0);
}
/**
* Evaluate expression and return a date
*
* @param cexp cexp
* @param context context
*
* @return type
*
* @throws FaultException FaultException
* @throws EvaluationException EvaluationException
*/
public Calendar evaluateAsDate(OExpression cexp, EvaluationContext context)
throws FaultException, EvaluationException {
List literal = DOMUtils.toList(evaluate(cexp, context,
XPathConstants.NODESET));
if (literal.size() == 0) {
throw new FaultException(cexp.getOwner().getConstants().getQnSelectionFailure(),
"No results for expression: " + cexp);
}
if (literal.size() > 1) {
throw new FaultException(cexp.getOwner().getConstants().getQnSelectionFailure(),
"Multiple results for expression: " + cexp);
}
Object date = literal.get(0);
if (date instanceof Calendar) {
return (Calendar) date;
}
if (date instanceof Date) {
Calendar cal = Calendar.getInstance();
cal.setTime((Date) date);
return cal;
}
if (date instanceof Element) {
date = ((Element) date).getTextContent();
}
try {
return ISO8601DateParser.parseCal(date.toString());
} catch (Exception ex) {
String errmsg = "Invalid date: " + literal;
__log.error(errmsg, ex);
throw new FaultException(cexp.getOwner().getConstants().getQnInvalidExpressionValue(),
errmsg);
}
}
/**
* Evaluate expression and return duration
*
* @param cexp cexp
* @param context context
*
* @return type
*
* @throws FaultException FaultException
* @throws EvaluationException EvaluationException
*/
public Duration evaluateAsDuration(OExpression cexp,
EvaluationContext context) throws FaultException, EvaluationException {
String literal = this.evaluateAsString(cexp, context);
try {
return new Duration(literal);
} catch (Exception ex) {
String errmsg = "Invalid duration: " + literal;
__log.error(errmsg, ex);
throw new FaultException(cexp.getOwner().getConstants().getQnInvalidExpressionValue(),
errmsg);
}
}
/**
* Evaluate expression and return opaque type
*
* @param cexp cexp
* @param ctx ctx
* @param type type
*
* @return type
*
* @throws FaultException FaultException
* @throws EvaluationException EvaluationException
*/
private Object evaluate(OExpression cexp, EvaluationContext ctx, QName type)
throws FaultException, EvaluationException {
try {
OXQuery10ExpressionBPEL20 oxquery10 = ((OXQuery10ExpressionBPEL20) cexp);
XQDataSource xqds = new SaxonXQDataSource();
XQConnection xqconn = xqds.getConnection();
Configuration configuration = ((SaxonXQConnection) xqconn).getConfiguration();
configuration.setAllNodesUntyped(true);
configuration.setHostLanguage(Configuration.XQUERY);
XQStaticContext staticEnv = xqconn.getStaticContext();
NSContext nsContext = oxquery10.getNamespaceCtx();
Set<String> prefixes = nsContext.getPrefixes();
for (String prefix : prefixes) {
String uri = nsContext.getNamespaceURI(prefix);
staticEnv.declareNamespace(prefix, uri);
}
configuration.setSchemaValidationMode(Validation.SKIP);
xqconn.setStaticContext(staticEnv);
// Prepare expression, for starters
String xquery = oxquery10.getXquery().replaceFirst(
Constants.XQUERY_FUNCTION_HANDLER_COMPILER,
Constants.XQUERY_FUNCTION_HANDLER_RUNTIME);
XQPreparedExpression exp = xqconn.prepareExpression(xquery);
JaxpFunctionResolver funcResolver = new JaxpFunctionResolver(ctx,
oxquery10);
JaxpVariableResolver variableResolver = new JaxpVariableResolver(ctx,
oxquery10, configuration);
// Bind external variables to runtime values
for (QName variable : exp.getAllUnboundExternalVariables()) {
// Evaluate referenced variable
Object value = variableResolver.resolveVariable(variable);
if (value instanceof Value) {
SaxonXQConnection saxonConn = (SaxonXQConnection) xqconn;
try {
Item item = ((Value) value).asItem();
if (item == null) {
exp.bindSequence(variable, xqconn.createSequence(Collections.EMPTY_LIST.iterator()));
} else {
XQItem item2 = new SaxonXQItem(item, saxonConn);
exp.bindItem(variable, item2);
}
} catch (XPathException e) {
__log.warn("", e);
}
} else {
if (value instanceof Date) {
Date d = (Date) value;
value = org.apache.ode.utils.ISO8601DateParser.format(d);
}
// Figure out type of variable
XQSequenceType xqType = getItemType(xqconn, value);
// Saxon doesn't like binding sequences to variables
if (value instanceof Node) {
// a node is a node-list, but the inverse isn't true.
// so, if the value is truly a node, leave it alone.
} else if (value instanceof NodeList) {
// So extract the first item from the node list
NodeList nodeList = (NodeList) value;
ArrayList nodeArray = new ArrayList();
for (int i = 0; i < nodeList.getLength(); i++) {
nodeArray.add(nodeList.item(i));
}
value = xqconn.createSequence(nodeArray.iterator());
}
// Bind value with external variable
if (value != null && xqType != null) {
if (value instanceof XQSequence) {
exp.bindSequence(variable, (XQSequence) value);
} else {
if (xqType instanceof XQItemType) {
exp.bindObject(variable, value, (XQItemType) xqType);
}
}
}
}
}
// Set context node
Node contextNode = (ctx.getRootNode() == null)
? DOMUtils.newDocument() : ctx.getRootNode();
contextNode.setUserData(XQuery10BpelFunctions.USER_DATA_KEY_FUNCTION_RESOLVER,
funcResolver, null);
exp.bindItem(XQConstants.CONTEXT_ITEM,
xqconn.createItemFromNode(contextNode, xqconn.createNodeType()));
// Execute query
XQResultSequence result = exp.executeQuery();
// Cast Saxon result to Java result
Object evalResult = getResultValue(type, result);
if ((evalResult != null) && __log.isDebugEnabled()) {
__log.debug("Expression " + cexp.toString() +
" generated result " + evalResult + " - type=" +
evalResult.getClass().getName());
if (ctx.getRootNode() != null) {
__log.debug("Was using context node " +
DOMUtils.domToString(ctx.getRootNode()));
}
}
return evalResult;
} catch (XQException xqe) {
// Extracting the real cause from all this wrapping isn't a simple task
Throwable cause = (xqe.getCause() != null) ? xqe.getCause() : xqe;
if (cause instanceof DynamicError) {
Throwable th = ((DynamicError) cause).getException();
if (th != null) {
cause = th;
if (cause.getCause() != null) {
cause = cause.getCause();
}
}
}
throw new EvaluationException(
"Error while executing an XQuery expression: " + cause.toString(), cause);
} catch (WrappedResolverException wre) {
__log.debug("Could not evaluate expression because of ", wre);
throw (FaultException) wre.getCause();
}
}
/**
* Return opaque object embedded in XQuery Item
*
* @param item item
*
* @return type
*
* @throws XQException XQException
*/
private Object getItemValue(XQItem item) throws XQException {
XQItemType itemType = item.getItemType();
Object itemValue = null;
switch (itemType.getBaseType()) {
case XQItemType.XQBASETYPE_BOOLEAN:
itemValue = item.getBoolean();
break;
case XQItemType.XQBASETYPE_DECIMAL:
itemValue = item.getDouble();
break;
case XQItemType.XQBASETYPE_BYTE:
itemValue = item.getByte();
break;
case XQItemType.XQBASETYPE_FLOAT:
itemValue = item.getFloat();
break;
case XQItemType.XQBASETYPE_INT:
case XQItemType.XQBASETYPE_INTEGER:
itemValue = item.getInt();
break;
case XQItemType.XQBASETYPE_LONG:
itemValue = item.getLong();
break;
case XQItemType.XQBASETYPE_ANYTYPE:
itemValue = item.getNode();
break;
case XQItemType.XQBASETYPE_ANYURI:
itemValue = item.getNodeUri();
break;
case XQItemType.XQBASETYPE_SHORT:
itemValue = item.getShort();
break;
case XQItemType.XQBASETYPE_STRING:
case XQItemType.XQBASETYPE_ANYATOMICTYPE:
itemValue = item.getAtomicValue();
break;
}
return itemValue;
}
/**
* Return XQuery type corresponding to given value
*
* @param xqconn XQuery connection
* @param value value
*
* @return type
*
* @throws XQException XQException
*/
private XQSequenceType getItemType(XQConnection xqconn, Object value) throws XQException {
XQSequenceType xqType = null;
if (value instanceof Long) {
xqType = xqconn.createAtomicType(XQItemType.XQBASETYPE_LONG);
} else if (value instanceof String) {
xqType = xqconn.createAtomicType(XQItemType.XQBASETYPE_STRING);
} else if (value instanceof Boolean) {
xqType = xqconn.createAtomicType(XQItemType.XQBASETYPE_BOOLEAN);
} else if (value instanceof Date) {
xqType = xqconn.createAtomicType(XQItemType.XQBASETYPE_DATETIME);
} else if (value instanceof BigDecimal) {
xqType = xqconn.createAtomicType(XQItemType.XQBASETYPE_DECIMAL);
} else if (value instanceof Float) {
xqType = xqconn.createAtomicType(XQItemType.XQBASETYPE_FLOAT);
} else if (value instanceof URI) {
xqType = xqconn.createAtomicType(XQItemType.XQBASETYPE_ANYURI);
} else if (value instanceof QName) {
xqType = xqconn.createAtomicType(XQItemType.XQBASETYPE_QNAME);
} else if (value instanceof BigInteger) {
xqType = xqconn.createAtomicType(XQItemType.XQBASETYPE_INT);
} else if (value instanceof Integer) {
xqType = xqconn.createAtomicType(XQItemType.XQBASETYPE_INTEGER);
} else if (value instanceof Double) {
xqType = xqconn.createAtomicType(XQItemType.XQBASETYPE_DOUBLE);
} else if (value instanceof Byte) {
xqType = xqconn.createAtomicType(XQItemType.XQBASETYPE_BYTE);
} else if (value instanceof Node) {
xqType = xqconn.createNodeType();
} else if (value instanceof NodeList || value instanceof XQSequence) {
XQItemType xqItemType = xqconn.createNodeType();
xqType = xqconn.createSequenceType(xqItemType, XQSequenceType.OCC_ZERO_OR_MORE);
}
return xqType;
}
/**
* Cast XQuery sequence into an opaque list
*
* @param type type
* @param result result
*
* @return value
*
* @throws XQException XQException
*/
private Object getResultValue(QName type, XQResultSequence result) throws XQException {
Document document = DOMUtils.newDocument();
Object resultValue = null;
if (XPathConstants.NODESET.equals(type)) {
List list = new ArrayList();
while (result.next()) {
Object itemValue = getItemValue(result.getItem());
if (itemValue instanceof Document) {
itemValue = DOMUtils.cloneNode(document, ((Document) itemValue).getDocumentElement());
} else if (itemValue instanceof Node) {
itemValue = DOMUtils.cloneNode(document, (Node) itemValue);
}
if (itemValue != null) {
list.add(itemValue);
}
}
resultValue = list;
} else if (XPathConstants.NODE.equals(type)) {
XQItem item = null;
if (result.count() > 0) {
result.first();
if (result.isOnItem()) {
item = result.getItem();
}
}
if (item != null) {
resultValue = getItemValue(item);
if (resultValue instanceof Node) {
resultValue = DOMUtils.cloneNode(document, (Node) resultValue);
}
}
} else if (XPathConstants.STRING.equals(type)) {
resultValue = result.getSequenceAsString(new Properties());
} else if (XPathConstants.NUMBER.equals(type)) {
resultValue = result.getSequenceAsString(new Properties());
resultValue = Integer.parseInt((String) resultValue);
} else if (XPathConstants.BOOLEAN.equals(type)) {
resultValue = result.getSequenceAsString(new Properties());
resultValue = Boolean.parseBoolean((String) resultValue);
}
return resultValue;
}
}