blob: 8ccc2d9d5073da4e72cb28cd0e7af507791f3f26 [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.xpath20.compiler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.namespace.QName;
import javax.xml.transform.TransformerFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathFactoryConfigurationException;
import net.sf.saxon.om.Name11Checker;
import net.sf.saxon.om.NamespaceConstant;
import net.sf.saxon.xpath.XPathEvaluator;
import net.sf.saxon.xpath.XPathFactoryImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.ode.bpel.compiler.api.CompilationException;
import org.apache.ode.bpel.compiler.api.CompilerContext;
import org.apache.ode.bpel.compiler.api.ExpressionCompiler;
import org.apache.ode.bpel.compiler.bom.Expression;
import org.apache.ode.bpel.elang.xpath10.compiler.XPathMessages;
import org.apache.ode.bpel.elang.xpath10.compiler.XslCompilationErrorListener;
import org.apache.ode.bpel.elang.xpath20.obj.OXPath20ExpressionBPEL20;
import org.apache.ode.bpel.obj.OExpression;
import org.apache.ode.bpel.obj.OLValueExpression;
import org.apache.ode.utils.DOMUtils;
import org.apache.ode.utils.msg.MessageBundle;
import org.apache.ode.utils.xsl.XslTransformHandler;
import org.w3c.dom.Node;
/**
* XPath compiler based on the SAXON implementation.
* @author Matthieu Riou <mriou at apache dot org>
*/
public class XPath20ExpressionCompilerImpl implements ExpressionCompiler {
protected static final Logger __log = LoggerFactory.getLogger(XPath20ExpressionCompilerBPEL20.class);
protected String _bpelNS;
protected QName _qnLinkStatus;
protected QName _qnVarProp;
protected QName _qnVarData;
protected QName _qnXslTransform;
protected final XPathMessages __msgs = MessageBundle.getMessages(XPathMessages.class);
protected Map<String, String> _properties = new HashMap<String, String>();
protected CompilerContext _compilerContext;
public XPath20ExpressionCompilerImpl(String bpelNS) {
_bpelNS = bpelNS;
_qnLinkStatus = new QName(_bpelNS, Constants.EXT_FUNCTION_GETLINKSTATUS);
_qnVarProp = new QName(_bpelNS, Constants.EXT_FUNCTION_GETVARIABLEPROPERTY);
_qnVarData = new QName(_bpelNS, Constants.EXT_FUNCTION_GETVARIABLEDATA);
_qnXslTransform = new QName(_bpelNS, Constants.EXT_FUNCTION_DOXSLTRANSFORM);
_properties.put("runtime-class", "org.apache.ode.bpel.elang.xpath20.runtime.XPath20ExpressionRuntime");
TransformerFactory trsf = new net.sf.saxon.TransformerFactoryImpl();
XslTransformHandler.getInstance().setTransformerFactory(trsf);
}
public void setCompilerContext(CompilerContext compilerContext) {
_compilerContext = compilerContext;
XslCompilationErrorListener xe = new XslCompilationErrorListener(compilerContext);
XslTransformHandler.getInstance().setErrorListener(xe);
}
/**
* @see org.apache.ode.bpel.compiler.api.ExpressionCompiler#compileJoinCondition(java.lang.Object)
*/
public OExpression compileJoinCondition(Object source) throws CompilationException {
return _compile((Expression) source, true);
}
/**
* @see org.apache.ode.bpel.compiler.api.ExpressionCompiler#compile(java.lang.Object)
*/
public OExpression compile(Object source) throws CompilationException {
return _compile((Expression) source, false);
}
/**
* @see org.apache.ode.bpel.compiler.api.ExpressionCompiler#compileLValue(java.lang.Object)
*/
public OLValueExpression compileLValue(Object source) throws CompilationException {
return (OLValueExpression) _compile((Expression) source, false);
}
/**
* @see org.apache.ode.bpel.compiler.api.ExpressionCompiler#compile(java.lang.Object)
*/
private OExpression _compile(org.apache.ode.bpel.compiler.bom.Expression xpath, boolean isJoinCondition)
throws CompilationException {
OXPath20ExpressionBPEL20 oexp = new OXPath20ExpressionBPEL20(_compilerContext.getOProcess(), _qnVarData,
_qnVarProp, _qnLinkStatus, _qnXslTransform, isJoinCondition);
oexp.setNamespaceCtx(_compilerContext.tryCacheNamespaceContext(xpath.getNamespaceContext()));
doJaxpCompile(oexp, xpath);
return oexp;
}
private void doJaxpCompile(OXPath20ExpressionBPEL20 out, Expression source) throws CompilationException {
String xpathStr;
Node node = source.getExpression();
if (node == null) {
throw new CompilationException(__msgs.errEmptyExpression(source.getURI(), new QName(source.getElement().getNamespaceURI(), source.getElement().getNodeName())));
}
if (node.getNodeType() != Node.TEXT_NODE && node.getNodeType() != Node.CDATA_SECTION_NODE) {
throw new CompilationException(__msgs.errUnexpectedNodeTypeForXPath(DOMUtils.domToString(node)));
}
xpathStr = node.getNodeValue();
xpathStr = xpathStr.trim();
if (xpathStr.length() == 0) {
throw new CompilationException(__msgs.warnXPath20Syntax(DOMUtils.domToString(node), "empty string"));
}
out.setXpath(xpathStr);
try {
__log.debug("Compiling expression " + xpathStr);
XPathFactory xpf = new XPathFactoryImpl();
JaxpFunctionResolver funcResolver = new JaxpFunctionResolver(
_compilerContext, out, source.getNamespaceContext(), _bpelNS);
JaxpVariableResolver varResolver = new JaxpVariableResolver(_compilerContext, out);
XPath xpe = xpf.newXPath();
xpe.setXPathFunctionResolver(funcResolver);
xpe.setXPathVariableResolver(varResolver);
xpe.setNamespaceContext(source.getNamespaceContext());
XPathExpression expr = xpe.compile(xpathStr);
// evaluate the expression so as to initialize the variables
try {
expr.evaluate(node);
} catch (XPathExpressionException xpee) {
// swallow errors caused by uninitialized variable
}
for (String varExpr : extractVariableExprs(xpathStr)) {
expr = xpe.compile(varExpr);
try {
expr.evaluate(node);
} catch (XPathExpressionException xpee) {
// swallow errors caused by uninitialized variable
}
}
for (String functionExpr : extractFunctionExprs(xpathStr)) {
expr = xpe.compile(functionExpr);
try {
expr.evaluate(node);
} catch (XPathExpressionException xpee) {
// swallow errors caused by uninitialized variable
}
}
} catch (XPathExpressionException e) {
__log.debug("",e);
__log.info("Couldn't validate properly expression " + xpathStr);
} catch (WrappedResolverException wre) {
if (wre._compilationMsg != null)
throw new CompilationException(wre._compilationMsg, wre);
if (wre.getCause() instanceof CompilationException)
throw (CompilationException) wre.getCause();
throw wre;
}
}
/**
* Returns the list of variable references in the given XPath expression
* that may not have been resolved properly, which is the case especially
* if the expression contains a function, which short circuited the evaluation.
*
* @param xpathStr
* @return list of variable expressions that may not have been resolved properly
*/
private List<String> extractVariableExprs(String xpathStr) {
ArrayList<String> variableExprs = new ArrayList<String>();
int firstVariable = xpathStr.indexOf("$"),
lastVariable = xpathStr.lastIndexOf("$"),
firstFunction = xpathStr.indexOf("(");
StringBuffer variableExpr = new StringBuffer();
if ((firstVariable > 0 && // the xpath references a variable
firstFunction > 0) || // the xpath contains a function
(firstVariable < lastVariable)) { // the xpath references multiple variables
// most likely, the variable reference has not been resolved, so make that happen
boolean quoted = false, doubleQuoted = false, variable = false;
Name11Checker nameChecker = Name11Checker.getInstance();
for (int index = 0; index < xpathStr.length(); index++) {
char ch = xpathStr.charAt(index);
if (ch == '\''){
quoted = !quoted;
}
if (ch == '\"') {
doubleQuoted = !doubleQuoted;
}
if (quoted || doubleQuoted){
continue;
}
if (ch == '$') {
variable = true;
variableExpr.setLength(0);
variableExpr.append(ch);
} else {
if (variable) {
variableExpr.append(ch);
// in the name is qualified, don't check if its a qname when we're at the ":" character
if (ch == ':') {
continue;
}
if (index == xpathStr.length() ||
!nameChecker.isQName(variableExpr.substring(1))) {
variable = false;
variableExpr.setLength(variableExpr.length() - 1);
variableExprs.add(variableExpr.toString());
variableExpr.setLength(0);
}
}
}
}
if (variableExpr.length() > 0) {
variableExprs.add(variableExpr.toString());
}
}
return variableExprs;
}
/**
* Returns the list of function references in the given XPath expression
* that may not have been resolved properly, which is the case especially
* if the expression contains a preceding function, which short circuited the evaluation.
*
* @param xpathStr
* @return list of function expressions that may not have been resolved properly
*/
private List<String> extractFunctionExprs(String xpathStr) {
ArrayList<String> functionExprs = new ArrayList<String>();
// Match the prefix : function name ( all contents except the ) and the closing )'s that may occur
// final String FUNCTION_REGEX = "\\w+:\\w+\\([.[^\\)]]*\\)*";
final String FUNCTION_REGEX = "(\\w+:)?\\w+\\((.+)?\\)";
int firstFunction = xpathStr.indexOf("("),
lastFunction = xpathStr.lastIndexOf("(");
if ((firstFunction > 0 && firstFunction < lastFunction)) {
Pattern regex = Pattern.compile(FUNCTION_REGEX);
Matcher matcher = regex.matcher(xpathStr);
while (matcher.find()) {
String function = matcher.group();
functionExprs.add(function);
}
}
return functionExprs;
}
public Map<String, String> getProperties() {
return _properties;
}
}