001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019: package org.apache.synapse.mediators.xquery;
020:
021: import net.sf.saxon.javax.xml.xquery.*;
022: import net.sf.saxon.xqj.SaxonXQDataSource;
023: import org.apache.axiom.om.OMElement;
024: import org.apache.axiom.om.OMNode;
025: import org.apache.axiom.om.impl.builder.StAXOMBuilder;
026: import org.apache.axiom.om.impl.dom.DOOMAbstractFactory;
027: import org.apache.axiom.om.util.ElementHelper;
028: import org.apache.axiom.om.xpath.AXIOMXPath;
029: import org.apache.axiom.soap.SOAP11Constants;
030: import org.apache.axiom.soap.SOAP12Constants;
031: import org.apache.synapse.MessageContext;
032: import org.apache.synapse.SynapseException;
033: import org.apache.synapse.config.Entry;
034: import org.apache.synapse.config.SynapseConfigUtils;
035: import org.apache.synapse.mediators.AbstractMediator;
036: import org.apache.synapse.mediators.MediatorProperty;
037: import org.jaxen.JaxenException;
038: import org.w3c.dom.Element;
039: import org.xml.sax.InputSource;
040:
041: import javax.xml.namespace.QName;
042: import javax.xml.stream.XMLInputFactory;
043: import javax.xml.stream.XMLStreamException;
044: import javax.xml.transform.dom.DOMSource;
045: import java.io.StringReader;
046: import java.util.ArrayList;
047: import java.util.List;
048:
049: /**
050: * The XQueryMediator provides the means to extract and manipulate data from XML documents using
051: * XQuery . It is possible to query against the current SOAP Message or external XML. To query
052: * against the current SOAP Message ,it is need to define custom variable with any name and type as
053: * element,document,document_element By providing a expression ,It is possible to select a custom
054: * node for querying.The all the variable that have defined in the mediator will be available
055: * during the query process .Basic variable can use bind basic type.
056: * currently only support * string,int,byte,short,double,long,float and boolean * types.
057: * Custom Variable can use to bind XML documents ,SOAP payload and any basic type which create
058: * through the XPath expression .
059: */
060:
061: public class XQueryMediator extends AbstractMediator {
062:
063: /**
064: * Properties that must set to the XQDataSource
065: */
066: private List dataSourceProperties = new ArrayList();
067:
068: /**
069: * The key for lookup the xquery
070: */
071: private String queryKey;
072:
073: /**
074: * The source of the xquery
075: */
076: private String querySource;
077:
078: /**
079: * The default xpath to get the first child of the SOAPBody
080: */
081: // public static final String DEFAULT_XPATH = "//s11:Envelope/s11:Body/child::*[position()=1] | " +
082: // "//s12:Envelope/s12:Body/child::*[position()=1]";
083: public static final String DEFAULT_XPATH = "s11:Body/child::*[position()=1] | "
084: + "s12:Body/child::*[position()=1]";
085:
086: /**
087: * The (optional) XPath expression which yeilds the target element to attached the result
088: */
089: private AXIOMXPath target = null;
090:
091: /**
092: * The list of variables for binding to the DyanamicContext in order to available for querying
093: */
094: private List variables = new ArrayList();
095:
096: /**
097: * Lock used to ensure thread-safe lookup of the object from the registry
098: */
099: private final Object resourceLock = new Object();
100:
101: /**
102: * Is it need to use DOMSource and DOMResult?
103: */
104: private boolean useDOMSource = false;
105:
106: /**
107: * The DataSource which use to create a connection to XML database
108: */
109: private XQDataSource cachedXQDataSource = null;
110:
111: /**
112: * connection with a specific XQuery engine.Connection will live as long as synapse live
113: */
114: private XQConnection cachedConnection = null;
115:
116: /**
117: * An expression that use for multiple executions.Expression will recreate if query has changed
118: */
119: private XQPreparedExpression cachedPreparedExpression = null;
120:
121: public XQueryMediator() {
122: // create the default XPath
123: try {
124: this .target = new AXIOMXPath(DEFAULT_XPATH);
125: this .target.addNamespace("s11",
126: SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI);
127: this .target.addNamespace("s12",
128: SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI);
129: } catch (JaxenException e) {
130: handleException("Error creating target XPath expression", e);
131: }
132: }
133:
134: /**
135: * Performs the query and attached the result to the target Node
136: *
137: * @param synCtx The current message
138: * @return true always
139: */
140: public boolean mediate(MessageContext synCtx) {
141:
142: try {
143:
144: boolean traceOn = isTraceOn(synCtx);
145: boolean traceOrDebugOn = isTraceOrDebugOn(traceOn);
146:
147: if (traceOrDebugOn) {
148: traceOrDebug(traceOn, "Start : XQuery mediator");
149:
150: if (traceOn && trace.isTraceEnabled()) {
151: trace.trace("Message : " + synCtx.getEnvelope());
152: }
153: traceOrDebug(traceOn,
154: "Performing XQuery using query resource with key : "
155: + queryKey);
156: }
157:
158: // perform the xquery
159: performQuery(synCtx, traceOrDebugOn, traceOn);
160:
161: if (traceOrDebugOn) {
162: traceOrDebug(traceOn, "End : XQuery mediator");
163: }
164:
165: return true;
166:
167: } catch (Exception e) {
168: handleException("Unable to execute the query "
169: + querySource, e);
170: }
171: return false;
172: }
173:
174: /**
175: * Perform the quering and get the result and attached to the target node
176: *
177: * @param synCtx The current MessageContext
178: * @param traceOrDebugOn is tracing or debbug on
179: * @param traceOn indicate whether trace is ON or OFF
180: */
181: private void performQuery(MessageContext synCtx,
182: boolean traceOrDebugOn, boolean traceOn) {
183:
184: boolean reLoad = false;
185: boolean needBind = false;
186: XQResultSequence resultSequence;
187:
188: Entry dp = synCtx.getConfiguration().getEntryDefinition(
189: queryKey);
190: // if the queryKey refers to a dynamic resource
191: if (dp != null && dp.isDynamic()) {
192: if (!dp.isCached() || dp.isExpired()) {
193: reLoad = true;
194: }
195: }
196:
197: try {
198: synchronized (resourceLock) {
199:
200: //creating data source
201: if (cachedXQDataSource == null) {
202: // A factory for XQConnection objects
203: cachedXQDataSource = new SaxonXQDataSource();
204: //setting up the properties to the XQDataSource
205: if (dataSourceProperties != null
206: && !dataSourceProperties.isEmpty()) {
207: if (traceOrDebugOn) {
208: traceOrDebug(traceOn,
209: "Setting up properties to the XQDataSource");
210: }
211: for (int i = 0; i < dataSourceProperties.size(); i++) {
212: MediatorProperty prop = (MediatorProperty) dataSourceProperties
213: .get(i);
214: if (prop != null) {
215: cachedXQDataSource.setProperty(prop
216: .getName(), prop.getValue());
217: }
218: }
219: }
220: }
221:
222: //creating connection
223: if (cachedConnection == null
224: || (cachedConnection != null && cachedConnection
225: .isClosed())) {
226: //get the Connection to XML DataBase
227: if (traceOrDebugOn) {
228: traceOrDebug(traceOn,
229: "Creating a connection from the XQDataSource ");
230: }
231: cachedConnection = cachedXQDataSource
232: .getConnection();
233: }
234:
235: // prepare the expression to execute query
236: if (reLoad
237: || querySource == null
238: || cachedPreparedExpression == null
239: || (cachedPreparedExpression != null && cachedPreparedExpression
240: .isClosed())) {
241:
242: Object o = synCtx.getEntry(queryKey);
243: if (o instanceof OMElement) {
244: querySource = ((OMElement) (o)).getText();
245: } else if (o instanceof String) {
246: querySource = (String) o;
247: }
248:
249: if (querySource != null) {
250:
251: if (traceOrDebugOn) {
252: traceOrDebug(traceOn,
253: "Picked up the xquery source "
254: + querySource
255: + "from the key "
256: + queryKey);
257: traceOrDebug(traceOn,
258: "Prepare an expression for the query ");
259: }
260:
261: //create an XQPreparedExpression using the query source
262: cachedPreparedExpression = cachedConnection
263: .prepareExpression(querySource);
264: // need binding because the expression just has recreated
265: needBind = true;
266:
267: } else {
268:
269: if (traceOrDebugOn) {
270: traceOrDebug(traceOn,
271: "Couldn't find the xquery source with a key "
272: + queryKey);
273: }
274: return;
275: }
276: }
277:
278: //Bind the external variables to the DynamicContext
279: if (variables != null & !variables.isEmpty()) {
280: if (traceOrDebugOn) {
281: traceOrDebug(traceOn,
282: "Binding external variables to the DynamicContext");
283: }
284: for (int i = 0; i < variables.size(); i++) {
285: MediatorVariable variable = (MediatorVariable) variables
286: .get(i);
287: boolean hasValueChanged = variable
288: .evaluateValue(synCtx);
289: //if the value has changed or need binding because the expression has recreated
290: if (hasValueChanged || needBind) {
291: //Binds the external variable to the DynamicContext
292: bindVariable(cachedPreparedExpression,
293: variable, traceOrDebugOn, traceOn);
294: }
295: }
296: }
297:
298: //executing the query
299: resultSequence = cachedPreparedExpression
300: .executeQuery();
301:
302: }
303:
304: if (resultSequence == null) {
305: if (traceOrDebugOn) {
306: traceOrDebug(traceOn, "Result Sequence is null");
307: }
308: return;
309: }
310:
311: //processing the result
312: while (resultSequence.next()) {
313:
314: XQItem xqItem = resultSequence.getItem();
315: if (xqItem == null) {
316: return;
317: }
318: XQItemType itemType = xqItem.getItemType();
319: if (itemType == null) {
320: return;
321: }
322: int itemKind = itemType.getItemKind();
323: int baseType = itemType.getBaseType();
324: if (traceOrDebugOn) {
325: traceOrDebug(traceOn, "The XQuery Result "
326: + xqItem.getItemAsString());
327: }
328:
329: //The target node that is going to modify
330: OMNode destination = getTargetNode(synCtx);
331: if (destination != null) {
332: if (traceOrDebugOn) {
333: traceOrDebug(traceOn, "The target node "
334: + destination);
335: }
336:
337: //If the result is XML
338: if (XQItemType.XQITEMKIND_DOCUMENT_ELEMENT == itemKind
339: || XQItemType.XQITEMKIND_ELEMENT == itemKind
340: || XQItemType.XQITEMKIND_DOCUMENT == itemKind) {
341: StAXOMBuilder builder = new StAXOMBuilder(
342: XMLInputFactory
343: .newInstance()
344: .createXMLStreamReader(
345: new StringReader(
346: xqItem
347: .getItemAsString())));
348: OMElement resultOM = builder
349: .getDocumentElement();
350: if (resultOM != null) {
351: //replace the target node from the result
352: destination.insertSiblingAfter(resultOM);
353: destination.detach();
354: }
355: } else if (XQItemType.XQBASETYPE_INTEGER == baseType
356: || XQItemType.XQBASETYPE_INT == baseType) {
357: //replace the text value of the target node by the result ,If the result is
358: // a basic type
359: ((OMElement) destination).setText(String
360: .valueOf(xqItem.getInt()));
361: } else if (XQItemType.XQBASETYPE_BOOLEAN == baseType) {
362: ((OMElement) destination).setText(String
363: .valueOf(xqItem.getBoolean()));
364: } else if (XQItemType.XQBASETYPE_DOUBLE == baseType) {
365: ((OMElement) destination).setText(String
366: .valueOf(xqItem.getDouble()));
367: } else if (XQItemType.XQBASETYPE_FLOAT == baseType) {
368: ((OMElement) destination).setText(String
369: .valueOf(xqItem.getFloat()));
370: } else if (XQItemType.XQBASETYPE_LONG == baseType) {
371: ((OMElement) destination).setText(String
372: .valueOf(xqItem.getLong()));
373: } else if (XQItemType.XQBASETYPE_SHORT == baseType) {
374: ((OMElement) destination).setText(String
375: .valueOf(xqItem.getShort()));
376: } else if (XQItemType.XQBASETYPE_BYTE == baseType) {
377: ((OMElement) destination).setText(String
378: .valueOf(xqItem.getByte()));
379: } else if (XQItemType.XQBASETYPE_STRING == baseType) {
380: ((OMElement) destination).setText(String
381: .valueOf(xqItem.getItemAsString()));
382: }
383: }
384: break; // Only take the *first* value of the result sequence
385: }
386: resultSequence.close(); // closing the result sequence
387: } catch (XQException e) {
388: handleException("Error during the querying "
389: + e.getMessage(), e);
390: } catch (XMLStreamException e) {
391: handleException(
392: "Error during retrieving the Doument Node as the result "
393: + e.getMessage(), e);
394: }
395: }
396:
397: /**
398: * Binding a variable to the Dynamic Context in order to available during doing the querying
399: *
400: * @param xqDynamicContext The Dynamic Context to which the variable will be binded
401: * @param variable The variable which contains the name and vaule for binding
402: * @param traceOrDebugOn is tracing or debbug on
403: * @param traceOn indicate whether trace is ON or OF
404: * @throws XQException throws if any error occurs when binding the variable
405: */
406: private void bindVariable(XQDynamicContext xqDynamicContext,
407: MediatorVariable variable, boolean traceOrDebugOn,
408: boolean traceOn) throws XQException {
409:
410: if (variable != null) {
411:
412: QName name = variable.getName();
413: int type = variable.getType();
414: Object value = variable.getValue();
415:
416: if (value != null && type != -1) {
417:
418: if (traceOrDebugOn) {
419: traceOrDebug(traceOn,
420: "Binding a variable to the DynamicContext with a name : "
421: + name + " and a value : " + value);
422: }
423:
424: switch (type) {
425: //Binding the basic type As-Is and XML element as an InputSource
426: case (XQItemType.XQBASETYPE_BOOLEAN): {
427: boolean booleanValue = false;
428: if (value instanceof String) {
429: booleanValue = Boolean
430: .parseBoolean((String) value);
431: } else if (value instanceof Boolean) {
432: booleanValue = ((Boolean) value).booleanValue();
433: } else {
434: handleException("Incompatible type for the Boolean");
435: }
436: xqDynamicContext.bindBoolean(name, booleanValue,
437: null);
438: break;
439: }
440: case (XQItemType.XQBASETYPE_INTEGER): {
441: int intValue = -1;
442: if (value instanceof String) {
443: try {
444: intValue = Integer.parseInt((String) value);
445: } catch (NumberFormatException e) {
446: handleException("Incompatible value '"
447: + value + "' " + "for the Integer",
448: e);
449: }
450: } else if (value instanceof Integer) {
451: intValue = ((Integer) value).intValue();
452: } else {
453: handleException("Incompatible type for the Integer");
454: }
455: if (intValue != -1) {
456: xqDynamicContext.bindInt(name, intValue, null);
457: }
458: break;
459: }
460: case (XQItemType.XQBASETYPE_INT): {
461: int intValue = -1;
462: if (value instanceof String) {
463: try {
464: intValue = Integer.parseInt((String) value);
465: } catch (NumberFormatException e) {
466: handleException("Incompatible value '"
467: + value + "' for the Int", e);
468: }
469: } else if (value instanceof Integer) {
470: intValue = ((Integer) value).intValue();
471: } else {
472: handleException("Incompatible type for the Int");
473: }
474: if (intValue != -1) {
475: xqDynamicContext.bindInt(name, intValue, null);
476: }
477: break;
478: }
479: case (XQItemType.XQBASETYPE_LONG): {
480: long longValue = -1;
481: if (value instanceof String) {
482: try {
483: longValue = Long.parseLong((String) value);
484: } catch (NumberFormatException e) {
485: handleException("Incompatible value '"
486: + value + "' " + "for the long ", e);
487: }
488: } else if (value instanceof Long) {
489: longValue = ((Long) value).longValue();
490: } else {
491: handleException("Incompatible type for the Long");
492: }
493: if (longValue != -1) {
494: xqDynamicContext
495: .bindLong(name, longValue, null);
496: }
497: break;
498: }
499: case (XQItemType.XQBASETYPE_SHORT): {
500: short shortValue = -1;
501: if (value instanceof String) {
502: try {
503: shortValue = Short
504: .parseShort((String) value);
505: } catch (NumberFormatException e) {
506: handleException("Incompatible value '"
507: + value + "' " + "for the short ",
508: e);
509: }
510: } else if (value instanceof Short) {
511: shortValue = ((Short) value).shortValue();
512: } else {
513: handleException("Incompatible type for the Short");
514: }
515: if (shortValue != -1) {
516: xqDynamicContext.bindShort(name, shortValue,
517: null);
518: }
519: break;
520: }
521: case (XQItemType.XQBASETYPE_DOUBLE): {
522: double doubleValue = -1;
523: if (value instanceof String) {
524: try {
525: doubleValue = Double
526: .parseDouble((String) value);
527: } catch (NumberFormatException e) {
528: handleException("Incompatible value '"
529: + value + "' " + "for the double ",
530: e);
531: }
532: } else if (value instanceof Double) {
533: doubleValue = ((Double) value).doubleValue();
534: } else {
535: handleException("Incompatible type for the Double");
536: }
537: if (doubleValue != -1) {
538: xqDynamicContext.bindDouble(name, doubleValue,
539: null);
540: }
541: break;
542: }
543: case (XQItemType.XQBASETYPE_FLOAT): {
544: float floatValue = -1;
545: if (value instanceof String) {
546: try {
547: floatValue = Float
548: .parseFloat((String) value);
549: } catch (NumberFormatException e) {
550: handleException("Incompatible value '"
551: + value + "' " + "for the float ",
552: e);
553: }
554: } else if (value instanceof Float) {
555: floatValue = ((Float) value).floatValue();
556: } else {
557: handleException("Incompatible type for the Float");
558: }
559: if (floatValue != -1) {
560: xqDynamicContext.bindFloat(name, floatValue,
561: null);
562: }
563: break;
564: }
565: case (XQItemType.XQBASETYPE_BYTE): {
566: byte byteValue = -1;
567: if (value instanceof String) {
568: try {
569: byteValue = Byte.parseByte((String) value);
570: } catch (NumberFormatException e) {
571: handleException("Incompatible value '"
572: + value + "' " + "for the byte ", e);
573: }
574: } else if (value instanceof Byte) {
575: byteValue = ((Byte) value).byteValue();
576: } else {
577: handleException("Incompatible type for the Byte");
578: }
579: if (byteValue != -1) {
580: xqDynamicContext
581: .bindByte(name, byteValue, null);
582: }
583: break;
584: }
585: case (XQItemType.XQBASETYPE_STRING): {
586: if (value instanceof String) {
587: xqDynamicContext.bindObject(name, value, null);
588: } else {
589: handleException("Incompatible type for the String");
590: }
591: break;
592: }
593: case (XQItemType.XQITEMKIND_DOCUMENT): {
594: if (value instanceof OMNode) {
595: if (useDOMSource) {
596: xqDynamicContext
597: .bindObject(
598: name,
599: new DOMSource(
600: ((Element) ElementHelper
601: .importOMElement(
602: (OMElement) value,
603: DOOMAbstractFactory
604: .getOMFactory()))
605: .getOwnerDocument()),
606: null);
607: } else {
608: xqDynamicContext.bindDocument(name,
609: new InputSource(SynapseConfigUtils
610: .getInputStream(value)));
611: }
612: }
613: break;
614: }
615: case (XQItemType.XQITEMKIND_ELEMENT): {
616: if (value instanceof OMNode) {
617: if (useDOMSource) {
618: xqDynamicContext
619: .bindObject(
620: name,
621: new DOMSource(
622: ((Element) ElementHelper
623: .importOMElement(
624: (OMElement) value,
625: DOOMAbstractFactory
626: .getOMFactory()))
627: .getOwnerDocument()),
628: null);
629: } else {
630: xqDynamicContext.bindDocument(name,
631: new InputSource(SynapseConfigUtils
632: .getInputStream(value)));
633: }
634: }
635: break;
636: }
637: case (XQItemType.XQITEMKIND_DOCUMENT_ELEMENT): {
638: if (value instanceof OMNode) {
639: if (useDOMSource) {
640: xqDynamicContext
641: .bindObject(
642: name,
643: new DOMSource(
644: ((Element) ElementHelper
645: .importOMElement(
646: (OMElement) value,
647: DOOMAbstractFactory
648: .getOMFactory()))
649: .getOwnerDocument()),
650: null);
651: } else {
652: xqDynamicContext.bindDocument(name,
653: new InputSource(SynapseConfigUtils
654: .getInputStream(value)));
655: }
656: }
657: break;
658: }
659: default: {
660: handleException("Unsupported type for the binding type"
661: + type + " in the variable name " + name);
662: break;
663: }
664: }
665: }
666: }
667:
668: }
669:
670: /**
671: * Return the OMNode to be used for the attached the query result. If a target XPath is not specified,
672: * this will default to the first child of the SOAP body i.e. - //*:Envelope/*:Body/child::*
673: *
674: * @param synCtx the message context
675: * @return the OMNode against which the result should be attached
676: */
677: public OMNode getTargetNode(MessageContext synCtx) {
678: try {
679: Object o = target.evaluate(synCtx.getEnvelope());
680: if (o instanceof OMNode) {
681: return (OMNode) o;
682: } else if (o instanceof List && !((List) o).isEmpty()) {
683: Object nodeObject = ((List) o).get(0); // Always fetches *only* the first
684: if (nodeObject instanceof OMNode) {
685: return (OMNode) nodeObject;
686: } else {
687: handleException("The evaluation of the XPath expression "
688: + target + " must target in an OMNode");
689: }
690: } else {
691: handleException("The evaluation of the XPath expression "
692: + target + " must target in an OMNode");
693: }
694: } catch (JaxenException e) {
695: handleException("Error evaluating XPath " + target
696: + " on message" + synCtx.getEnvelope());
697: }
698: return null;
699: }
700:
701: private void handleException(String msg, Exception e) {
702: log.error(msg, e);
703: throw new SynapseException(msg, e);
704: }
705:
706: private void handleException(String msg) {
707: log.error(msg);
708: throw new SynapseException(msg);
709: }
710:
711: public String getQueryKey() {
712: return queryKey;
713: }
714:
715: public void setQueryKey(String queryKey) {
716: this .queryKey = queryKey;
717: }
718:
719: public String getQuerySource() {
720: return querySource;
721: }
722:
723: public void setQuerySource(String querySource) {
724: this .querySource = querySource;
725: }
726:
727: public void addAllVariables(List list) {
728: this .variables.addAll(list);
729: }
730:
731: public void addVariable(MediatorVariable variable) {
732: this .variables.add(variable);
733: }
734:
735: public List getDataSourceProperties() {
736: return dataSourceProperties;
737: }
738:
739: public List getVariables() {
740: return variables;
741: }
742:
743: public AXIOMXPath getTarget() {
744: return target;
745: }
746:
747: public void setTarget(AXIOMXPath target) {
748: this .target = target;
749: }
750:
751: public void addAllDataSoureProperties(List list) {
752: this .dataSourceProperties.addAll(list);
753: }
754:
755: public boolean isUseDOMSource() {
756: return useDOMSource;
757: }
758:
759: public void setUseDOMSource(boolean useDOMSource) {
760: this.useDOMSource = useDOMSource;
761: }
762: }
|