/* * 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.mediators.xquery; import net.sf.saxon.s9api.*; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.OMNode; import org.apache.axiom.om.OMText; import org.apache.axiom.om.impl.builder.StAXOMBuilder; import org.apache.axiom.om.impl.dom.DOOMAbstractFactory; import org.apache.axiom.om.util.ElementHelper; import org.apache.synapse.MessageContext; import org.apache.synapse.SynapseException; import org.apache.synapse.SynapseLog; import org.apache.synapse.config.Entry; import org.apache.synapse.config.SynapseConfigUtils; import org.apache.synapse.mediators.AbstractMediator; import org.apache.synapse.mediators.MediatorProperty; import org.apache.synapse.mediators.Value; import org.apache.synapse.util.xpath.SourceXPathSupport; import org.apache.synapse.util.xpath.SynapseXPath; import org.w3c.dom.Element; import javax.activation.DataHandler; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamSource; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.util.*; /** * The XQueryMediator provides the means to extract and manipulate data from XML documents using * XQuery . It is possible to query against the current SOAP Message or external XML. To query * against the current SOAP Message ,it is need to define custom variable with any name and type as * element,document,document_element By providing a expression ,It is possible to select a custom * node for querying.The all the variable that have defined in the mediator will be available * during the query process .Basic variable can use bind basic type. * currently only support * string,int,byte,short,double,long,float and boolean * types. * Custom Variable can use to bind XML documents ,SOAP payload and any basic type which create * through the XPath expression . */ public class XQueryMediator extends AbstractMediator { /* Properties that must set to the Processor */ private final List<MediatorProperty> processorProperties = new ArrayList<MediatorProperty>(); /* The key for lookup the xquery (Supports both static and dynamic keys)*/ private Value queryKey; /* The source of the xquery */ private String querySource; /*The target node*/ private final SourceXPathSupport target = new SourceXPathSupport(); /* The list of variables for binding to the DyanamicContext in order to available for querying */ private final List<MediatorVariable> variables = new ArrayList<MediatorVariable>(); /*Lock used to ensure thread-safe lookup of the object from the registry */ private final Object resourceLock = new Object(); /* Is it need to use DOMSource and DOMResult? */ private boolean useDOMSource = false; /*The Processor allows global Saxon configuration options to be set & it acts as a factory for generating XQuery compiler */ private Processor cachedProcessor = null; /* XQueryCompiler allows to compile XQuery 1.0 queries */ private XQueryCompiler cachedQueryCompiler = null; /* An XQueryEvaluator that use for represents a compiled and loaded query ready for execution. XQueryEvaluator will recreate if query has changed */ private Map<String, XQueryEvaluator> cachedXQueryEvaluatorMap = new Hashtable<String, XQueryEvaluator>(); public XQueryMediator() { } /** * Performs the query and attached the result to the target Node * * @param synCtx The current message * @return true always */ public boolean mediate(MessageContext synCtx) { try { if (synCtx.getEnvironment().isDebuggerEnabled()) { if (super.divertMediationRoute(synCtx)) { return true; } } SynapseLog synLog = getLog(synCtx); if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("Start : XQuery mediator"); if (synLog.isTraceTraceEnabled()) { synLog.traceTrace("Message : " + synCtx.getEnvelope()); } synLog.traceOrDebug("Performing XQuery using query resource with key : " + queryKey); } // perform the xquery performQuery(synCtx, synLog); synLog.traceOrDebug("End : XQuery mediator"); return true; } catch (Exception e) { handleException("Unable to execute the query ", e); } return false; } /** * Perform the quering and get the result and attached to the target node * * @param synCtx The current MessageContext * @param synLog the Synapse log to use */ private void performQuery(MessageContext synCtx, SynapseLog synLog) { boolean reLoad = false; boolean needSet = false; XQueryEvaluator queryEvaluator = null; String generatedQueryKey = null; XQueryExecutable xQueryExecutable = null; XdmValue xdmValue; boolean isQueryKeyGenerated = false; if (queryKey != null) { // Derive actual key from xpath or get static key generatedQueryKey = queryKey.evaluateValue(synCtx); } if (generatedQueryKey != null) { isQueryKeyGenerated = true; } if (generatedQueryKey != null && !"".equals(generatedQueryKey)) { Entry dp = synCtx.getConfiguration().getEntryDefinition(generatedQueryKey); // if the queryKey refers to a dynamic resource if (dp != null && dp.isDynamic()) { if (!dp.isCached() || dp.isExpired()) { reLoad = true; } } } try { synchronized (resourceLock) { //creating processor if (cachedProcessor == null) { cachedProcessor = new Processor(false); //setting up the properties to the Processor if (processorProperties != null && !processorProperties.isEmpty()) { synLog.traceOrDebug("Setting up properties to the XQDataSource"); for (MediatorProperty processorProperty : processorProperties) { if (processorProperty != null) { cachedProcessor.setConfigurationProperty(processorProperty.getName(), processorProperty.getValue()); } } } } //creating XQueryCompiler if (cachedQueryCompiler == null) { synLog.traceOrDebug("Creating a compiler from the Processor "); cachedQueryCompiler = cachedProcessor.newXQueryCompiler(); } //If already cached evaluator then load it from cachedXQueryEvaluatorMap if (isQueryKeyGenerated) { queryEvaluator = cachedXQueryEvaluatorMap.get(generatedQueryKey); } if (reLoad || queryEvaluator == null) { if (querySource != null && !"".equals(querySource)) { if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("Using in-lined query source - " + querySource); synLog.traceOrDebug("Prepare an expression for the query "); } xQueryExecutable = cachedQueryCompiler.compile(querySource); queryEvaluator = xQueryExecutable.load(); // if queryEvaluator is created then put it in to cachedXQueryEvaluatorMap if (isQueryKeyGenerated) { cachedXQueryEvaluatorMap.put(generatedQueryKey, queryEvaluator); } // need set because the expression just has recreated needSet = true; } else { Object o = synCtx.getEntry(generatedQueryKey); if (o == null) { if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("Couldn't find the xquery source with a key " + queryKey); } throw new SynapseException("No object found for the key '" + generatedQueryKey + "'"); } String sourceCode = null; InputStream inputStream = null; if (o instanceof OMElement) { sourceCode = ((OMElement) (o)).getText(); } else if (o instanceof String) { sourceCode = (String) o; } else if (o instanceof OMText) { DataHandler dataHandler = (DataHandler) ((OMText) o).getDataHandler(); if (dataHandler != null) { try { inputStream = dataHandler.getInputStream(); if (inputStream == null) { if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("Couldn't get" + " the stream from the xquery source with a key " + queryKey); } return; } } catch (IOException e) { handleException("Error in reading content as a stream "); } } } if ((sourceCode == null || "".equals(sourceCode)) && inputStream == null) { if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("Couldn't find the xquery source with a key " + queryKey); } return; } if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("Picked up the xquery source from the " + "key " + queryKey); synLog.traceOrDebug("Prepare an expression for the query "); } try { if (sourceCode != null) { //create an xQueryExecutable using the query source xQueryExecutable = cachedQueryCompiler.compile(sourceCode); } else { xQueryExecutable = cachedQueryCompiler.compile(inputStream); } } catch (IOException e) { handleException("Error during the query inputStream compilation"); } queryEvaluator = xQueryExecutable.load(); // if queryEvaluator is created then put it in to cachedXQueryEvaluatorMap if (isQueryKeyGenerated) { cachedXQueryEvaluatorMap.put(generatedQueryKey, queryEvaluator); } // need set because the evaluator just has recreated needSet = true; } } //Set the external variables to the queryEvaluator if (variables != null && !variables.isEmpty()) { synLog.traceOrDebug("Binding external variables to the DynamicContext"); for (MediatorVariable variable : variables) { if (variable != null) { boolean hasValueChanged = variable.evaluateValue(synCtx); //if the value has changed or need set because the evaluator has recreated if (hasValueChanged || needSet) { //Set the external variable to the queryEvaluator setVariable(queryEvaluator, variable, synLog); } } } } //executing the query xdmValue = queryEvaluator.evaluate(); } if (queryEvaluator == null) { synLog.traceOrDebug("Result Sequence is null"); return; } //processing the result for (XdmItem xdmItem : xdmValue) { if (xdmItem == null) { return; } XdmNodeKind xdmNodeKind = null; ItemType itemType = null; if (xdmItem.isAtomicValue()) { itemType = getItemType(xdmItem, cachedProcessor); if (itemType == null) { return; } } else { xdmNodeKind = ((XdmNode) xdmItem).getNodeKind(); } if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("The XQuery Result " + xdmItem.toString()); } //The target node that is going to modify OMNode destination = target.selectOMNode(synCtx, synLog); if (destination != null) { if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("The target node " + destination); } //If the result is XML if (XdmNodeKind.DOCUMENT == xdmNodeKind || XdmNodeKind.ELEMENT == xdmNodeKind) { StAXOMBuilder builder = new StAXOMBuilder(XMLInputFactory.newInstance().createXMLStreamReader( new StringReader(xdmItem.toString()))); OMElement resultOM = builder.getDocumentElement(); if (resultOM != null) { //replace the target node from the result destination.insertSiblingAfter(resultOM); destination.detach(); } } else if (ItemType.INTEGER == itemType || ItemType.INT == itemType) { //replace the text value of the target node by the result ,If the result is // a basic type ((OMElement) destination).setText(String.valueOf(((XdmAtomicValue) xdmItem).getDecimalValue().intValue())); } else if (ItemType.BOOLEAN == itemType) { ((OMElement) destination).setText(String.valueOf(((XdmAtomicValue) xdmItem).getBooleanValue())); } else if (ItemType.DOUBLE == itemType) { ((OMElement) destination).setText(String.valueOf(((XdmAtomicValue) xdmItem).getDoubleValue())); } else if (ItemType.FLOAT == itemType) { ((OMElement) destination).setText(String.valueOf(((XdmAtomicValue) xdmItem).getDecimalValue().floatValue())); } else if (ItemType.LONG == itemType) { ((OMElement) destination).setText(String.valueOf(((XdmAtomicValue) xdmItem).getLongValue())); } else if (ItemType.SHORT == itemType) { ((OMElement) destination).setText(String.valueOf(((XdmAtomicValue) xdmItem).getDecimalValue().shortValue())); } else if (ItemType.BYTE == itemType) { ((OMElement) destination).setText(String.valueOf(((XdmAtomicValue) xdmItem).getDecimalValue().byteValue())); } else if (ItemType.STRING == itemType) { ((OMElement) destination).setText(String.valueOf(((XdmAtomicValue) xdmItem).getValue())); } } else if (null == target.getXPath() && null == destination) { //In the case soap body doesn't have the first element --> Empty soap body destination = synCtx.getEnvelope().getBody(); if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("The target node " + destination); } //If the result is XML if (XdmNodeKind.ELEMENT == xdmNodeKind || XdmNodeKind.DOCUMENT == xdmNodeKind) { StAXOMBuilder builder = new StAXOMBuilder( XMLInputFactory.newInstance().createXMLStreamReader( new StringReader(xdmItem.toString()))); OMElement resultOM = builder.getDocumentElement(); if (resultOM != null) { ((OMElement) destination).addChild(resultOM); } } //No else part since soap body could have only XML part not text values } break; // Only take the *first* value of the result sequence } queryEvaluator.close(); // closing the result sequence } catch (SaxonApiException e) { handleException("Error during the querying " + e.getMessage(), e); } catch (XMLStreamException e) { handleException("Error during retrieving the Document Node as the result " + e.getMessage(), e); } } /** * Binding a variable to the Dynamic Context in order to available during doing the querying * * @param queryEvaluator The XQuery evaluator to which the variable will be added * @param variable The variable which contains the name and vaule for adding * @param synLog the Synapse log to use * @throws SaxonApiException throws if any error occurs when adding the variable */ private void setVariable(XQueryEvaluator queryEvaluator, MediatorVariable variable, SynapseLog synLog) throws SaxonApiException { QName name = new QName(variable.getName().getLocalPart()); if (variable != null) { ItemType type = variable.getType(); XdmNodeKind nodeKind = variable.getNodeKind(); Object value = variable.getValue(); if (value != null && (type != null || nodeKind != null)) { if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("Binding a variable to the DynamicContext with a name : " + name + " and a value : " + value); } //Binding the basic type As-Is and XML element as an InputSource if (ItemType.BOOLEAN == type) { boolean booleanValue = false; if (value instanceof String) { booleanValue = Boolean.parseBoolean((String) value); } else if (value instanceof Boolean) { booleanValue = (Boolean) value; } else { handleException("Incompatible type for the Boolean"); } queryEvaluator.setExternalVariable(name, new XdmAtomicValue(String.valueOf(booleanValue), ItemType.BOOLEAN)); } else if (ItemType.INTEGER == type) { int intValue = -1; if (value instanceof String) { try { intValue = Integer.parseInt((String) value); } catch (NumberFormatException e) { handleException("Incompatible value '" + value + "' " + "for the Integer", e); } } else if (value instanceof Integer) { intValue = (Integer) value; } else { handleException("Incompatible type for the Integer"); } if (intValue != -1) { queryEvaluator.setExternalVariable(name, new XdmAtomicValue(String.valueOf(intValue), ItemType.INTEGER)); } } else if (ItemType.INT == type) { int intValue = -1; if (value instanceof String) { try { intValue = Integer.parseInt((String) value); } catch (NumberFormatException e) { handleException("Incompatible value '" + value + "' for the Int", e); } } else if (value instanceof Integer) { intValue = (Integer) value; } else { handleException("Incompatible type for the Int"); } if (intValue != -1) { queryEvaluator.setExternalVariable(name, new XdmAtomicValue(String.valueOf(intValue), ItemType.INT)); } } else if (ItemType.LONG == type) { long longValue = -1; if (value instanceof String) { try { longValue = Long.parseLong((String) value); } catch (NumberFormatException e) { handleException("Incompatible value '" + value + "' " + "for the long ", e); } } else if (value instanceof Long) { longValue = (Long) value; } else { handleException("Incompatible type for the Long"); } if (longValue != -1) { queryEvaluator.setExternalVariable(name, new XdmAtomicValue(String.valueOf(longValue), ItemType.LONG)); } } else if (ItemType.SHORT == type) { short shortValue = -1; if (value instanceof String) { try { shortValue = Short.parseShort((String) value); } catch (NumberFormatException e) { handleException("Incompatible value '" + value + "' " + "for the short ", e); } } else if (value instanceof Short) { shortValue = (Short) value; } else { handleException("Incompatible type for the Short"); } if (shortValue != -1) { queryEvaluator.setExternalVariable(name, new XdmAtomicValue(String.valueOf(shortValue), ItemType.SHORT)); } } else if (ItemType.DOUBLE == type) { double doubleValue = -1; if (value instanceof String) { try { doubleValue = Double.parseDouble((String) value); } catch (NumberFormatException e) { handleException("Incompatible value '" + value + "' " + "for the double ", e); } } else if (value instanceof Double) { doubleValue = (Double) value; } else { handleException("Incompatible type for the Double"); } if (doubleValue != -1) { queryEvaluator.setExternalVariable(name, new XdmAtomicValue(String.valueOf(doubleValue), ItemType.DOUBLE)); } } else if (ItemType.FLOAT == type) { float floatValue = -1; if (value instanceof String) { try { floatValue = Float.parseFloat((String) value); } catch (NumberFormatException e) { handleException("Incompatible value '" + value + "' " + "for the float ", e); } } else if (value instanceof Float) { floatValue = (Float) value; } else { handleException("Incompatible type for the Float"); } if (floatValue != -1) { queryEvaluator.setExternalVariable(name, new XdmAtomicValue(String.valueOf(floatValue), ItemType.FLOAT)); } } else if (ItemType.BYTE == type) { byte byteValue = -1; if (value instanceof String) { try { byteValue = Byte.parseByte((String) value); } catch (NumberFormatException e) { handleException("Incompatible value '" + value + "' " + "for the byte ", e); } } else if (value instanceof Byte) { byteValue = (Byte) value; } else { handleException("Incompatible type for the Byte"); } if (byteValue != -1) { queryEvaluator.setExternalVariable(name, new XdmAtomicValue(String.valueOf(byteValue), ItemType.BYTE)); } } else if (ItemType.STRING == type) { if (value instanceof String) { queryEvaluator.setExternalVariable(name, new XdmAtomicValue(String.valueOf(value), ItemType.STRING)); } else { handleException("Incompatible type for the String"); } } else if (XdmNodeKind.DOCUMENT == nodeKind || XdmNodeKind.ELEMENT == nodeKind) { setOMNode(name, value, queryEvaluator, cachedProcessor); } else { handleException("Unsupported type for the binding type" + type + " in the variable name " + name); } } } else { /* The following block will invariably result in "javax.xml.xquery.XQException: Argument value is null", which is the proper behaviour for this case. This block was added to fix the issue where if a variable with non-null value is passed for evaluation and if the subsequent request has a null value for the same variable (i.e. with the same name), the previous non-null value is given out instead of considering the null-case. */ if (synLog.isTraceOrDebugEnabled()) { synLog.traceOrDebug("Null variable value encountered for variable name: " + name); } queryEvaluator.setExternalVariable(name, null); } } private void setOMNode(QName name, Object value, XQueryEvaluator queryEvaluator, Processor processor) throws SaxonApiException{ OMElement variableValue = null; if (value instanceof String) { variableValue = SynapseConfigUtils.stringToOM((String) value); } else if (value instanceof OMElement) { variableValue = (OMElement) value; } if (variableValue != null) { DocumentBuilder documentBuilder = processor.newDocumentBuilder(); if (useDOMSource) { XdmNode xdmNode = documentBuilder.build(new DOMSource(((Element) ElementHelper. importOMElement(variableValue, DOOMAbstractFactory.getOMFactory())). getOwnerDocument())); queryEvaluator.setExternalVariable(name, xdmNode); } else { StreamSource streamSource = new StreamSource(SynapseConfigUtils.getInputStream(variableValue)); XdmNode xdmNode = documentBuilder.build(streamSource); queryEvaluator.setExternalVariable(name, xdmNode); } } } private static ItemType getItemType(XdmItem item, Processor process)throws SaxonApiException{ return new ItemTypeFactory(process).getAtomicType(((XdmAtomicValue) item).getPrimitiveTypeName()); } private void handleException(String msg, Exception e) { log.error(msg, e); throw new SynapseException(msg, e); } private void handleException(String msg) { log.error(msg); throw new SynapseException(msg); } public Value getQueryKey() { return queryKey; } public void setQueryKey(Value queryKey) { this.queryKey = queryKey; } public String getQuerySource() { return querySource; } public void setQuerySource(String querySource) { this.querySource = querySource; } public void addAllVariables(List<MediatorVariable> list) { this.variables.addAll(list); } public void addVariable(MediatorVariable variable) { this.variables.add(variable); } public List<MediatorProperty> getProcessorProperties() { return processorProperties; } public List<MediatorVariable> getVariables() { return variables; } public SynapseXPath getTarget() { return target.getXPath(); } public void setTarget(SynapseXPath source) { this.target.setXPath(source); } public void addAllDataSourceProperties(List<MediatorProperty> list) { this.processorProperties.addAll(list); } public boolean isUseDOMSource() { return useDOMSource; } public void setUseDOMSource(boolean useDOMSource) { this.useDOMSource = useDOMSource; } public boolean isContentAltering() { return true; } }