| /* |
| * 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.javax.xml.xquery.*; |
| import net.sf.saxon.xqj.SaxonXQDataSource; |
| import org.apache.axiom.om.OMAbstractFactory; |
| import org.apache.axiom.om.OMElement; |
| import org.apache.axiom.om.OMException; |
| import org.apache.axiom.om.OMNode; |
| import org.apache.axiom.om.OMText; |
| import org.apache.axiom.om.OMXMLBuilderFactory; |
| import org.apache.axiom.om.OMXMLParserWrapper; |
| 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 org.xml.sax.InputSource; |
| |
| import javax.activation.DataHandler; |
| import javax.xml.namespace.QName; |
| import javax.xml.transform.dom.DOMSource; |
| 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 XQDataSource */ |
| private final List<MediatorProperty> dataSourceProperties = 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 DataSource which use to create a connection to XML database */ |
| private XQDataSource cachedXQDataSource = null; |
| |
| /* connection with a specific XQuery engine.Connection will live as long as synapse live */ |
| private XQConnection cachedConnection = null; |
| |
| /* An expression that use for multiple executions.Expression will recreate if query has changed */ |
| private Map<String, XQPreparedExpression> cachedPreparedExpressionMap = new Hashtable<String, XQPreparedExpression>(); |
| |
| 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 { |
| |
| 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 needBind = false; |
| XQResultSequence resultSequence; |
| String generatedQueryKey = null; |
| boolean isQueryKeyGenerated = false; |
| |
| if (queryKey != null) { |
| // Derive actual key from xpath or get static key |
| generatedQueryKey = queryKey.evaluateValue(synCtx); |
| } |
| |
| if (generatedQueryKey != null) { |
| isQueryKeyGenerated = true; |
| } |
| |
| // get expression from generatedQueryKey |
| XQPreparedExpression cachedPreparedExpression = null; |
| |
| 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 data source |
| if (cachedXQDataSource == null) { |
| // A factory for XQConnection objects |
| cachedXQDataSource = new SaxonXQDataSource(); |
| //setting up the properties to the XQDataSource |
| if (dataSourceProperties != null && !dataSourceProperties.isEmpty()) { |
| synLog.traceOrDebug("Setting up properties to the XQDataSource"); |
| for (MediatorProperty dataSourceProperty : dataSourceProperties) { |
| if (dataSourceProperty != null) { |
| cachedXQDataSource.setProperty(dataSourceProperty.getName(), |
| dataSourceProperty.getValue()); |
| } |
| } |
| } |
| } |
| |
| //creating connection |
| if (cachedConnection == null |
| || (cachedConnection != null && cachedConnection.isClosed())) { |
| //get the Connection to XML DataBase |
| synLog.traceOrDebug("Creating a connection from the XQDataSource "); |
| cachedConnection = cachedXQDataSource.getConnection(); |
| } |
| |
| //If already cached expression then load it from cachedPreparedExpressionMap |
| if (isQueryKeyGenerated) { |
| cachedPreparedExpression = cachedPreparedExpressionMap.get(generatedQueryKey); |
| } |
| |
| // prepare the expression to execute query |
| if (reLoad || cachedPreparedExpression == null |
| || (cachedPreparedExpression != null |
| && cachedPreparedExpression.isClosed())) { |
| |
| if (querySource != null && !"".equals(querySource)) { |
| |
| if (cachedPreparedExpression == null) { |
| |
| if (synLog.isTraceOrDebugEnabled()) { |
| synLog.traceOrDebug("Using in-lined query source - " + querySource); |
| synLog.traceOrDebug("Prepare an expression for the query "); |
| } |
| |
| //create an XQPreparedExpression using the query source |
| cachedPreparedExpression = |
| cachedConnection.prepareExpression(querySource); |
| |
| // if cachedPreparedExpression is created then put it in to cachedPreparedExpressionMap |
| if (isQueryKeyGenerated) { |
| cachedPreparedExpressionMap.put(generatedQueryKey, cachedPreparedExpression); |
| } |
| |
| // need binding because the expression just has recreated |
| needBind = 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); |
| } |
| return; |
| } |
| |
| 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 "); |
| } |
| |
| if (sourceCode != null) { |
| //create an XQPreparedExpression using the query source |
| cachedPreparedExpression = |
| cachedConnection.prepareExpression(sourceCode); |
| } else { |
| //create an XQPreparedExpression using the query source stream |
| cachedPreparedExpression = |
| cachedConnection.prepareExpression(inputStream); |
| } |
| |
| // if cachedPreparedExpression is created then put it in to cachedPreparedExpressionMap |
| if (isQueryKeyGenerated) { |
| cachedPreparedExpressionMap.put(generatedQueryKey, cachedPreparedExpression); |
| } |
| |
| // need binding because the expression just has recreated |
| needBind = true; |
| } |
| } |
| |
| //Bind the external variables to the DynamicContext |
| 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 binding because the expression has recreated |
| if (hasValueChanged || needBind) { |
| //Binds the external variable to the DynamicContext |
| bindVariable(cachedPreparedExpression, variable, synLog); |
| } |
| } |
| } |
| } |
| |
| //executing the query |
| resultSequence = cachedPreparedExpression.executeQuery(); |
| |
| } |
| |
| if (resultSequence == null) { |
| synLog.traceOrDebug("Result Sequence is null"); |
| return; |
| } |
| |
| //processing the result |
| while (resultSequence.next()) { |
| |
| XQItem xqItem = resultSequence.getItem(); |
| if (xqItem == null) { |
| return; |
| } |
| XQItemType itemType = xqItem.getItemType(); |
| if (itemType == null) { |
| return; |
| } |
| int itemKind = itemType.getItemKind(); |
| int baseType = itemType.getBaseType(); |
| if (synLog.isTraceOrDebugEnabled()) { |
| synLog.traceOrDebug("The XQuery Result " + xqItem.getItemAsString()); |
| } |
| |
| //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 (XQItemType.XQITEMKIND_DOCUMENT_ELEMENT == itemKind || |
| XQItemType.XQITEMKIND_ELEMENT == itemKind || |
| XQItemType.XQITEMKIND_DOCUMENT == itemKind) { |
| OMXMLParserWrapper builder = OMXMLBuilderFactory.createOMBuilder( |
| new StringReader(xqItem.getItemAsString())); |
| OMElement resultOM = builder.getDocumentElement(); |
| if (resultOM != null) { |
| //replace the target node from the result |
| destination.insertSiblingAfter(resultOM); |
| destination.detach(); |
| } |
| } else if (XQItemType.XQBASETYPE_INTEGER == baseType || |
| XQItemType.XQBASETYPE_INT == baseType) { |
| //replace the text value of the target node by the result ,If the result is |
| // a basic type |
| ((OMElement) destination).setText(String.valueOf(xqItem.getInt())); |
| } else if (XQItemType.XQBASETYPE_BOOLEAN == baseType) { |
| ((OMElement) destination).setText(String.valueOf(xqItem.getBoolean())); |
| } else if (XQItemType.XQBASETYPE_DOUBLE == baseType) { |
| ((OMElement) destination).setText(String.valueOf(xqItem.getDouble())); |
| } else if (XQItemType.XQBASETYPE_FLOAT == baseType) { |
| ((OMElement) destination).setText(String.valueOf(xqItem.getFloat())); |
| } else if (XQItemType.XQBASETYPE_LONG == baseType) { |
| ((OMElement) destination).setText(String.valueOf(xqItem.getLong())); |
| } else if (XQItemType.XQBASETYPE_SHORT == baseType) { |
| ((OMElement) destination).setText(String.valueOf(xqItem.getShort())); |
| } else if (XQItemType.XQBASETYPE_BYTE == baseType) { |
| ((OMElement) destination).setText(String.valueOf(xqItem.getByte())); |
| } else if (XQItemType.XQBASETYPE_STRING == baseType) { |
| ((OMElement) destination).setText( |
| String.valueOf(xqItem.getItemAsString())); |
| } |
| } else if (target.getXPath() == null) { |
| //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 (XQItemType.XQITEMKIND_DOCUMENT_ELEMENT == itemKind || |
| XQItemType.XQITEMKIND_ELEMENT == itemKind || |
| XQItemType.XQITEMKIND_DOCUMENT == itemKind) { |
| OMXMLParserWrapper builder = OMXMLBuilderFactory.createOMBuilder( |
| new StringReader(xqItem.getItemAsString())); |
| 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 |
| } |
| resultSequence.close(); // closing the result sequence |
| } catch (XQException e) { |
| handleException("Error during the querying " + e.getMessage(), e); |
| } catch (OMException e) { |
| handleException("Error during retrieving the Doument Node as the result " |
| + e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Binding a variable to the Dynamic Context in order to available during doing the querying |
| * |
| * @param xqDynamicContext The Dynamic Context to which the variable will be binded |
| * @param variable The variable which contains the name and vaule for binding |
| * @param synLog the Synapse log to use |
| * @throws XQException throws if any error occurs when binding the variable |
| */ |
| private void bindVariable(XQDynamicContext xqDynamicContext, MediatorVariable variable, |
| SynapseLog synLog) throws XQException { |
| |
| if (variable != null) { |
| |
| QName name = variable.getName(); |
| int type = variable.getType(); |
| Object value = variable.getValue(); |
| |
| if (value != null && type != -1) { |
| |
| if (synLog.isTraceOrDebugEnabled()) { |
| synLog.traceOrDebug("Binding a variable to the DynamicContext with a name : " |
| + name + " and a value : " + value); |
| } |
| |
| switch (type) { |
| //Binding the basic type As-Is and XML element as an InputSource |
| case (XQItemType.XQBASETYPE_BOOLEAN): { |
| 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"); |
| } |
| xqDynamicContext.bindBoolean(name, booleanValue, null); |
| break; |
| } |
| case (XQItemType.XQBASETYPE_INTEGER): { |
| 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) { |
| xqDynamicContext.bindInt(name, intValue, null); |
| } |
| break; |
| } |
| case (XQItemType.XQBASETYPE_INT): { |
| 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) { |
| xqDynamicContext.bindInt(name, intValue, null); |
| } |
| break; |
| } |
| case (XQItemType.XQBASETYPE_LONG): { |
| 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) { |
| xqDynamicContext.bindLong(name, longValue, null); |
| } |
| break; |
| } |
| case (XQItemType.XQBASETYPE_SHORT): { |
| 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) { |
| xqDynamicContext.bindShort(name, shortValue, null); |
| } |
| break; |
| } |
| case (XQItemType.XQBASETYPE_DOUBLE): { |
| 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) { |
| xqDynamicContext.bindDouble(name, doubleValue, null); |
| } |
| break; |
| } |
| case (XQItemType.XQBASETYPE_FLOAT): { |
| 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) { |
| xqDynamicContext.bindFloat(name, floatValue, null); |
| } |
| break; |
| } |
| case (XQItemType.XQBASETYPE_BYTE): { |
| 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) { |
| xqDynamicContext.bindByte(name, byteValue, null); |
| } |
| break; |
| } |
| case (XQItemType.XQBASETYPE_STRING): { |
| if (value instanceof String) { |
| xqDynamicContext.bindObject(name, value, null); |
| } else { |
| handleException("Incompatible type for the String"); |
| } |
| break; |
| } |
| case (XQItemType.XQITEMKIND_DOCUMENT): { |
| bindOMNode(name, value, xqDynamicContext); |
| break; |
| } |
| case (XQItemType.XQITEMKIND_ELEMENT): { |
| bindOMNode(name, value, xqDynamicContext); |
| break; |
| } |
| case (XQItemType.XQITEMKIND_DOCUMENT_ELEMENT): { |
| bindOMNode(name, value, xqDynamicContext); |
| break; |
| } |
| default: { |
| handleException("Unsupported type for the binding type" + type + |
| " in the variable name " + name); |
| break; |
| } |
| } |
| } |
| } |
| |
| } |
| |
| private void bindOMNode(QName name, Object value, |
| XQDynamicContext xqDynamicContext) throws XQException { |
| |
| OMElement variableValue = null; |
| if (value instanceof String) { |
| variableValue = SynapseConfigUtils.stringToOM((String) value); |
| } else if (value instanceof OMElement) { |
| variableValue = (OMElement) value; |
| } |
| |
| if (variableValue != null) { |
| if (useDOMSource) { |
| xqDynamicContext. |
| bindObject(name, |
| new DOMSource(((Element) ElementHelper. |
| importOMElement(variableValue, OMAbstractFactory.getMetaFactory( |
| OMAbstractFactory.FEATURE_DOM).getOMFactory())). |
| getOwnerDocument()), null); |
| } else { |
| xqDynamicContext.bindDocument(name, |
| new InputSource(SynapseConfigUtils.getInputStream( |
| variableValue))); |
| } |
| } |
| } |
| |
| 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> getDataSourceProperties() { |
| return dataSourceProperties; |
| } |
| |
| 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.dataSourceProperties.addAll(list); |
| } |
| |
| public boolean isUseDOMSource() { |
| return useDOMSource; |
| } |
| |
| public void setUseDOMSource(boolean useDOMSource) { |
| this.useDOMSource = useDOMSource; |
| } |
| } |