/*
* 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.synapse.util.xpath;
import org.apache.axiom.om.*;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axiom.om.impl.dom.DOOMAbstractFactory;
import org.apache.axiom.om.impl.llom.OMDocumentImpl;
import org.apache.axiom.om.impl.llom.OMElementImpl;
import org.apache.axiom.om.impl.llom.OMTextImpl;
import org.apache.axiom.om.impl.llom.util.AXIOMUtil;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.impl.dom.factory.DOMSOAPFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseConstants;
import org.apache.synapse.config.SynapsePropertiesLoader;
import org.apache.synapse.config.xml.SynapsePath;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.transport.passthru.config.PassThroughConfiguration;
import org.apache.synapse.util.streaming_xpath.StreamingXPATH;
import org.apache.synapse.util.streaming_xpath.compiler.exception.StreamingXPATHCompilerException;
import org.apache.synapse.util.streaming_xpath.custom.components.ParserComponent;
import org.apache.synapse.util.streaming_xpath.exception.StreamingXPATHException;
import org.jaxen.*;
import org.jaxen.util.SingletonList;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
/**
* <p>XPath that has been used inside Synapse xpath processing. This has a extension function named
* <code>get-property</code> which is use to retrieve message context properties with the given
* name from the function</p>
*
* <p>For example the following function <code>get-property('prop')</code> can be evaluatedd using
* an XPath to retrieve the message context property value with the name <code>prop</code>.</p>
*
* <p>Apart from that this XPath has a certain set of XPath variables associated with it. They are
* as follows;
* <dl>
* <dt><tt>body</tt></dt>
* <dd>The SOAP 1.1 or 1.2 body element.</dd>
* <dt><tt>header</tt></dt>
* <dd>The SOAP 1.1 or 1.2 header element.</dd>
* </dl>
* </p>
*
* <p>Also there are some XPath prefixes defined in <code>SynapseXPath</code> to access various
* properties using XPath variables, where the variable name represents the particular prefix and
* the property name as the local part of the variable. Those variables are;
* <dl>
* <dt><tt>ctx</tt></dt>
* <dd>Prefix for Synapse MessageContext properties</dd>
* <dt><tt>axis2</tt></dt>
* <dd>Prefix for Axis2 MessageContext properties</dd>
* <dt><tt>trp</tt></dt>
* <dd>Prefix for the transport headers</dd>
* </dl>
* </p>
*
* <p>This XPath is Thread Safe, and provides a special set of evaluate functions for the
* <code>MessageContext</code> and <code>SOAPEnvelope</code> as well as a method to retrieve
* string values of the evaluated XPaths</p>
*
* @see org.apache.axiom.om.xpath.AXIOMXPath
* @see #getContext(Object)
* @see org.apache.synapse.util.xpath.SynapseXPathFunctionContext
* @see org.apache.synapse.util.xpath.SynapseXPathVariableContext
*/
public class SynapseXPath extends SynapsePath {
private static final long serialVersionUID = 7639226137534334222L;
private static final Log log = LogFactory.getLog(SynapseXPath.class);
private javax.xml.xpath.XPath domXpath = XPathFactory.newInstance().newXPath();
private String domXpathConfig = SynapsePropertiesLoader.loadSynapseProperties().
getProperty(SynapseConstants.FAIL_OVER_DOM_XPATH_PROCESSING);
//Required to force stream XPath disable for some mediators, since the stream XPath
//has a limitation of extracting only the first element after its iterating a list so
//cases like PayloadMediators though the stream xPath enables we should use the old jaxen
//way of extracting variable.
private boolean forceDisableStreamXpath=false;
private String enableStreamingXpath = SynapsePropertiesLoader.loadSynapseProperties().
getProperty(SynapseConstants.STREAMING_XPATH_PROCESSING);
private StreamingXPATH streamingXPATH =null;
public String getEvaluator() {
return evaluator;
}
public void setEvaluator(String evaluator) {
this.evaluator = evaluator;
}
private String evaluator="null";
/**
* <p>Initializes the <code>SynapseXPath</code> with the given <code>xpathString</code> as the
* XPath</p>
*
* @param xpathString xpath in its string format
* @throws JaxenException in case of an initialization failure
*/
public SynapseXPath(String xpathString) throws JaxenException {
super(xpathString, log);
this.setPathType(SynapsePath.X_PATH);
this.expression = xpathString;
PassThroughConfiguration conf = PassThroughConfiguration.getInstance();
bufferSizeSupport =conf.getIOBufferSize();
// TODO: Improve this
if (xpathString.contains("/")) {
contentAware = true;
} else if (//xpathString.contains("get-property('To')") ||
xpathString.contains("get-property('From'") ||
xpathString.contains("get-property('FAULT')")) {
contentAware = true;
} else {
contentAware = false;
}
if(xpathString.contains("$trp") || xpathString.contains("$ctx") || xpathString.contains("$axis2")){
contentAware = false;
return;
}
if("true".equals(enableStreamingXpath)){
try {
this.streamingXPATH = new StreamingXPATH(xpathString);
contentAware = false;
} catch (StreamingXPATHException e) {
if (log.isDebugEnabled()) {
log.debug("Provided XPATH expression " + xpathString + " cant be evaluated custom.");
}
contentAware = true;
} catch (StreamingXPATHCompilerException exception) {
if (log.isDebugEnabled()) {
log.debug("Provided XPATH expression " + xpathString + " cant be evaluated custom.");
}
contentAware = true;
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Provided XPATH expression " + xpathString + " cant be evaluated custom.");
}
contentAware = true;
}
}
}
/**
* Construct an XPath expression from a given string and initialize its
* namespace context based on a given element.
*
* @param element The element that determines the namespace context of the
* XPath expression. See {@link #addNamespaces(OMElement)}
* for more details.
* @param xpathExpr the string representation of the XPath expression.
* @throws JaxenException if there is a syntax error while parsing the expression
* or if the namespace context could not be set up
*/
public SynapseXPath(OMElement element, String xpathExpr) throws JaxenException {
super(element, xpathExpr, log);
this.expression = xpathExpr;
this.setPathType(SynapsePath.X_PATH);
}
/**
* Construct an XPath expression from a given attribute.
* The string representation of the expression is taken from the attribute
* value, while the attribute's owner element is used to determine the
* namespace context of the expression.
*
* @param attribute the attribute to construct the expression from
* @throws JaxenException if there is a syntax error while parsing the expression
* or if the namespace context could not be set up
*/
public SynapseXPath(OMAttribute attribute) throws JaxenException {
super(attribute, log);
this.expression = attribute.getAttributeValue();
this.setPathType(SynapsePath.X_PATH);
}
public static SynapseXPath parseXPathString(String xPathStr) throws JaxenException {
if (xPathStr.indexOf('{') == -1) {
return new SynapseXPath(xPathStr);
}
int count = 0;
StringBuffer newXPath = new StringBuffer();
Map<String, String> nameSpaces = new HashMap<String, String>();
String curSegment = null;
boolean xPath = false;
StringTokenizer st = new StringTokenizer(xPathStr, "{}", true);
while (st.hasMoreTokens()) {
String s = st.nextToken();
if ("{".equals(s)) {
xPath = true;
} else if ("}".equals(s)) {
xPath = false;
String prefix = "rp" + count++;
nameSpaces.put(prefix, curSegment);
newXPath.append(prefix).append(":");
} else {
if (xPath) {
curSegment = s;
} else {
newXPath.append(s);
}
}
}
SynapseXPath synXPath = new SynapseXPath(newXPath.toString());
for (Map.Entry<String,String> entry : nameSpaces.entrySet()) {
synXPath.addNamespace(entry.getKey(), entry.getValue());
}
return synXPath;
}
/**
* <P>Evaluates the XPath expression against the MessageContext of the current message and
* returns a String representation of the result</p>
*
* @param synCtx the source message which holds the MessageContext against full context
* @return a String representation of the result of evaluation
*/
public String stringValueOf(MessageContext synCtx) {
try {
InputStream inputStream = null;
Object result = null;
org.apache.axis2.context.MessageContext axis2MC =null;
if (!forceDisableStreamXpath && "true".equals(enableStreamingXpath)&& streamingXPATH != null && (((Axis2MessageContext)synCtx).getEnvelope() == null || ((Axis2MessageContext)synCtx).getEnvelope().getBody().getFirstElement() == null)) {
try {
axis2MC = ((Axis2MessageContext)synCtx).getAxis2MessageContext();//((Axis2MessageContext) context).getAxis2MessageContext();
inputStream=getMessageInputStreamPT(axis2MC);
} catch (IOException e) {
e.printStackTrace();
}
if (inputStream != null) {
try {
result = streamingXPATH.getStringValue(inputStream);
} catch (XMLStreamException e) {
handleException("Error occurred while parsing the XPATH String", e);
} catch (StreamingXPATHException e) {
handleException("Error occurred while parsing the XPATH String", e);
}
} else {
try {
result = streamingXPATH.getStringValue(synCtx.getEnvelope());
} catch (XMLStreamException e) {
handleException("Error occurred while parsing the XPATH String", e);
} catch (StreamingXPATHException e) {
handleException("Error occurred while parsing the XPATH String", e);
}
}
} else {
result = evaluate(synCtx);
}
if (result == null) {
return null;
}
StringBuffer textValue = new StringBuffer();
if (result instanceof List) {
List list = (List) result;
for (Object o : list) {
if (o == null && list.size() == 1) {
return null;
}
if (o instanceof OMTextImpl) {
textValue.append(((OMTextImpl) o).getText());
} else if (o instanceof OMElementImpl) {
String s = ((OMElementImpl) o).getText();
// We use StringUtils.trim as String.trim does not remove U+00A0 (int 160) (No-break space)
if (s.replace(String.valueOf((char) 160), " ").trim().length() == 0) {
s = o.toString();
}
textValue.append(s);
} else if (o instanceof OMDocumentImpl) {
textValue.append(
((OMDocumentImpl) o).getOMDocumentElement().toString());
} else if (o instanceof OMAttribute) {
textValue.append(
((OMAttribute) o).getAttributeValue());
} else if (o instanceof SynapseXPath) {
textValue.append(
((SynapseXPath) o).stringValueOf(synCtx));
}
}
}else if("true".equals(enableStreamingXpath)&& streamingXPATH != null){
if(!"".equals((String) result)){
OMElement re=AXIOMUtil.stringToOM((String) result);
if(re!=null){
textValue.append(re.getText());
}
else{
textValue.append(result.toString());
}
}
}
else {
textValue.append(result.toString());
}
return textValue.toString();
} catch (UnresolvableException ex) {
//if fail-over xpath processing is set to true in synapse properties, perform
//xpath processing in DOM fashion which can support XPATH2.0 with supported XAPTH engine like SAXON
if ("true".equals(domXpathConfig)) {
if (log.isDebugEnabled()) {
log.debug("AXIOM xpath evaluation failed with UnresolvableException, " +
"trying to perform DOM based XPATH", ex);
}
try {
return evaluateDOMXPath(synCtx);
} catch (Exception e) {
handleException("Evaluation of the XPath expression " + this.toString() +
" resulted in an error", e);
}
} else {
handleException("Evaluation of the XPath expression " + this.toString() +
" resulted in an error", ex);
}
} catch (JaxenException je) {
handleException("Evaluation of the XPath expression " + this.toString() +
" resulted in an error", je);
} catch (XMLStreamException e) {
handleException("Evaluation of the XPath expression " + this.toString() +
" resulted in an error", e);
}
return null;
}
/**
* Specialized form of xpath evaluation function.An xpath evaluate() will be performed using two contexts
* (ie:-soap-envelope and on Synapse Message Context). This is useful for evaluating xpath on a
* nodeset for function contexts (we need both nodeset and synapse ctxts for evaluating function
* scope expressions)
* @param primaryContext a context object ie:- a soap envelope
* @param secondaryContext a context object ie:-synapse message ctxt
* @return result
*/
public Object evaluate(Object primaryContext, MessageContext secondaryContext) {
Object result = null;
//if result is still not found use second ctxt ie:-syn-ctxt with a wrapper to evaluate
if (secondaryContext != null) {
try {
//wrapper Context is used to evaluate 'dynamic' function scope objects
result = evaluate(new ContextWrapper((SOAPEnvelope) primaryContext,secondaryContext));
} catch (Exception e) {
handleException("Evaluation of the XPath expression " + this.toString() +
" resulted in an error", e);
}
} else {
try {
result = evaluate(primaryContext);
} catch (JaxenException e) {
handleException("Evaluation of the XPath expression " + this.toString() +
" resulted in an error", e);
}
}
return result;
}
public void addNamespace(OMNamespace ns) throws JaxenException {
addNamespace(ns.getPrefix(), ns.getNamespaceURI());
domNamespaceMap.addNamespace(ns.getPrefix(), ns.getNamespaceURI());
ParserComponent.addToNameSpaceMap(ns.getPrefix(), ns.getNamespaceURI());
}
/**
* Create a {@link Context} wrapper for the provided object.
* This methods implements the following class specific behavior:
* <dl>
* <dt>{@link MessageContext}</dt>
* <dd>The XPath expression is evaluated against the SOAP envelope
* and the functions and variables defined by
* {@link SynapseXPathFunctionContext} and
* {@link SynapseXPathVariableContext} are
* available.</dd>
* <dt>{@link SOAPEnvelope}</dt>
* <dd>The variables defined by {@link SynapseXPathVariableContext}
* are available.</dd>
* </dl>
* For all other object types, the behavior is identical to
* {@link BaseXPath#getContext(Object)}.
* <p>
* Note that the behavior described here also applies to all evaluation
* methods such as {@link #evaluate(Object)} or {@link #selectSingleNode(Object)},
* given that these methods all use {@link #getContext(Object)}.
*
* @see SynapseXPathFunctionContext#getFunction(String, String, String)
* @see SynapseXPathVariableContext#getVariableValue(String, String, String)
*/
@Override
protected Context getContext(Object obj) {
if (obj instanceof MessageContext) {
MessageContext synCtx = (MessageContext)obj;
ContextSupport baseContextSupport = getContextSupport();
ContextSupport contextSupport =
new ContextSupport(baseContextSupport.getNamespaceContext(),
new SynapseXPathFunctionContext(baseContextSupport.getFunctionContext(), synCtx),
new SynapseXPathVariableContext(baseContextSupport.getVariableContext(), synCtx),
baseContextSupport.getNavigator());
Context context = new Context(contextSupport);
context.setNodeSet(new SingletonList(synCtx.getEnvelope()));
return context;
} else if (obj instanceof SOAPEnvelope) {
SOAPEnvelope env = (SOAPEnvelope)obj;
ContextSupport baseContextSupport = getContextSupport();
ContextSupport contextSupport =
new ContextSupport(baseContextSupport.getNamespaceContext(),
baseContextSupport.getFunctionContext(),
new SynapseXPathVariableContext(baseContextSupport.getVariableContext(), env),
baseContextSupport.getNavigator());
Context context = new Context(contextSupport);
context.setNodeSet(new SingletonList(env));
return context;
} else if (obj instanceof ContextWrapper) {
ContextWrapper wrapper = (ContextWrapper) obj;
ContextSupport baseContextSupport = getContextSupport();
ContextSupport contextSupport =
new ContextSupport(baseContextSupport.getNamespaceContext(),
baseContextSupport.getFunctionContext(),
new SynapseXPathVariableContext(baseContextSupport.getVariableContext(), wrapper.getMessageCtxt(),
wrapper.getEnvelope()),
baseContextSupport.getNavigator());
Context context = new Context(contextSupport);
context.setNodeSet(new SingletonList(wrapper.getEnvelope()));
return context;
} else {
return super.getContext(obj);
}
}
public boolean isForceDisableStreamXpath() {
return forceDisableStreamXpath;
}
public void setForceDisableStreamXpath(boolean forceDisableStreamXpath) {
this.forceDisableStreamXpath = forceDisableStreamXpath;
}
/**
* This is a wrapper class used to inject both envelope and message contexts for xpath
* We use this to resolve function scope xpath variables
*/
private static class ContextWrapper{
private MessageContext ctxt;
private SOAPEnvelope env;
public ContextWrapper(SOAPEnvelope env, MessageContext ctxt){
this.env = env;
this.ctxt = ctxt;
}
public SOAPEnvelope getEnvelope() {
return env;
}
public MessageContext getMessageCtxt() {
return ctxt;
}
}
public synchronized String evaluateDOMXPath(MessageContext synCtx) throws XPathExpressionException {
OMElement element = synCtx.getEnvelope().getBody().getFirstElement();
OMElement doomElement;
if (element == null) {
doomElement = new DOMSOAPFactory().createOMElement(new QName(""));
} else {
doomElement = convertToDOOM(element);
}
domXpath.setNamespaceContext(domNamespaceMap);
domXpath.setXPathFunctionResolver(new GetPropertyFunctionResolver(synCtx));
domXpath.setXPathVariableResolver(new DOMSynapseXPathVariableResolver(this.getVariableContext(), synCtx));
XPathExpression expr = domXpath.compile(this.getRootExpr().getText());
Object result = expr.evaluate(doomElement);
if (result != null) {
return result.toString();
}
return null;
}
private OMElement convertToDOOM(OMElement element) {
XMLStreamReader llomReader = element.getXMLStreamReader();
OMFactory doomFactory = DOOMAbstractFactory.getOMFactory();
StAXOMBuilder doomBuilder = new StAXOMBuilder(doomFactory, llomReader);
return doomBuilder.getDocumentElement();
}
// private InputStream getMessageInputStreamBR(MessageContext context) throws IOException {
// InputStream temp;
// SOAPEnvelope envelope = context.getEnvelope();
// OMElement contentEle = envelope.getBody().getFirstElement();
//
// if (contentEle != null) {
//
// OMNode node = contentEle.getFirstOMChild();
//
// if (node != null && (node instanceof OMText)) {
//
// OMText binaryDataNode = (OMText) node;
// DataHandler dh = (DataHandler) binaryDataNode.getDataHandler();
// DataSource dataSource = dh.getDataSource();
//
// if (dataSource instanceof StreamingOnRequestDataSource) {
// // preserve the content while reading the incoming data stream
// ((StreamingOnRequestDataSource) dataSource).setLastUse(false);
// // forcing to consume the incoming data stream
// temp = dataSource.getInputStream();
// return temp;
// }
// }
// }
// return null;
// }
}