| /* |
| * 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; |
| } |
| } |