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:
020: package org.apache.synapse.mediators.transform;
021:
022: import org.apache.axiom.om.OMAbstractFactory;
023: import org.apache.axiom.om.OMElement;
024: import org.apache.axiom.om.OMFactory;
025: import org.apache.axiom.om.OMNode;
026: import org.apache.axiom.om.impl.builder.StAXOMBuilder;
027: import org.apache.axiom.om.impl.dom.DOOMAbstractFactory;
028: import org.apache.axiom.om.impl.dom.jaxp.DocumentBuilderFactoryImpl;
029: import org.apache.axiom.om.impl.llom.OMSourcedElementImpl;
030: import org.apache.axiom.om.util.ElementHelper;
031: import org.apache.axiom.om.util.StAXUtils;
032: import org.apache.axiom.soap.SOAP11Constants;
033: import org.apache.axiom.soap.SOAP12Constants;
034: import org.apache.axiom.soap.SOAPEnvelope;
035: import org.apache.axiom.soap.impl.builder.StAXSOAPModelBuilder;
036: import org.apache.axis2.AxisFault;
037: import org.apache.commons.io.IOUtils;
038: import org.apache.synapse.MessageContext;
039: import org.apache.synapse.SynapseException;
040: import org.apache.synapse.config.Entry;
041: import org.apache.synapse.config.SynapseConfigUtils;
042: import org.apache.synapse.mediators.AbstractMediator;
043: import org.apache.synapse.mediators.MediatorProperty;
044: import org.apache.synapse.transport.base.BaseConstants;
045: import org.apache.synapse.util.xpath.SynapseXPath;
046: import org.apache.synapse.util.TemporaryData;
047: import org.apache.synapse.util.TextFileDataSource;
048: import org.jaxen.JaxenException;
049: import org.w3c.dom.Element;
050: import org.w3c.dom.Node;
051:
052: import javax.xml.parsers.ParserConfigurationException;
053: import javax.xml.stream.XMLOutputFactory;
054: import javax.xml.stream.XMLStreamException;
055: import javax.xml.stream.XMLStreamReader;
056: import javax.xml.stream.XMLStreamWriter;
057: import javax.xml.transform.*;
058: import javax.xml.transform.dom.DOMResult;
059: import javax.xml.transform.dom.DOMSource;
060: import javax.xml.transform.stream.StreamResult;
061: import javax.xml.transform.stream.StreamSource;
062: import java.io.*;
063: import java.util.ArrayList;
064: import java.util.Iterator;
065: import java.util.List;
066:
067: /**
068: * The XSLT mediator performs an XSLT transformation requested, using
069: * the current message. The source attribute (if available) specifies the source element
070: * on which the transformation would be applied. It will default to the first child of
071: * the messages' SOAP body, if it is omitted.
072: *
073: * Additional properties passed into this mediator would become parameters for XSLT.
074: * Additional features passed into this mediator would become features except for
075: * "http://ws.apache.org/ns/synapse/transform/feature/dom" for the Transformer Factory, which
076: * is used to decide between using DOM and Streams during the transformation process. By default
077: * this is turned on as an optimization, but should be set to false if issues are detected
078: *
079: * Note: Set the TransformerFactory system property to generate and use translets
080: * -Djavax.xml.transform.TransformerFactory=org.apache.xalan.xsltc.trax.TransformerFactoryImpl
081: *
082: */
083: public class XSLTMediator extends AbstractMediator {
084:
085: /** Maximum size of a byte array stream attempted in-memory before file serialization is used */
086: private static final int BYTE_ARRAY_SIZE = 8192;
087: /**
088: * The feature for which deciding swiching between DOM and Stream during the
089: * transformation process
090: */
091: public static final String USE_DOM_SOURCE_AND_RESULTS = "http://ws.apache.org/ns/synapse/transform/feature/dom";
092: /**
093: * The resource key/name which refers to the XSLT to be used for the transformation
094: */
095: private String xsltKey = null;
096:
097: /** Variable to hold source XPath string to use for debugging */
098: private String sourceXPathString = null;
099:
100: /**
101: * The (optional) XPath expression which yields the source element for a transformation
102: */
103: private SynapseXPath source = null;
104:
105: /**
106: * The name of the message context property to store the transformation result
107: */
108: private String targetPropertyName = null;
109:
110: /**
111: * Any parameters which should be passed into the XSLT transformation
112: */
113: private List properties = new ArrayList();
114:
115: /**
116: * Any features which should be set to the TransformerFactory by explicitly
117: */
118: private List explicitFeatures = new ArrayList();
119:
120: /**
121: * The Template instance used to create a Transformer object. This is thread-safe
122: *
123: * @see javax.xml.transform.Templates
124: */
125: private Templates cachedTemplates = null;
126:
127: /**
128: * The TransformerFactory instance which use to create Templates...This is not thread-safe.
129: * @see javax.xml.transform.TransformerFactory
130: */
131: private final TransformerFactory transFact = TransformerFactory
132: .newInstance();
133:
134: /**
135: * Lock used to ensure thread-safe creation and use of the above Transformer
136: */
137: private final Object transformerLock = new Object();
138:
139: /**
140: * Is it need to use DOMSource and DOMResult?
141: */
142: private boolean useDOMSourceAndResults = false;
143:
144: /**
145: * Hold any TransformerException's encountered during transformation by the XSLT processor
146: */
147: private TransformerException transformerException = null;
148:
149: // todo - this is a hack to get the handler module case working - ruwan
150: // public static final String DEFAULT_XPATH = "//s11:Envelope/s11:Body/child::*[position()=1] | " +
151: // "//s12:Envelope/s12:Body/child::*[position()=1]";
152: public static final String DEFAULT_XPATH = "s11:Body/child::*[position()=1] | "
153: + "s12:Body/child::*[position()=1]";
154:
155: public XSLTMediator() {
156: // create the default XPath
157: try {
158: this .source = new SynapseXPath(DEFAULT_XPATH);
159: this .source.addNamespace("s11",
160: SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI);
161: this .source.addNamespace("s12",
162: SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI);
163: } catch (JaxenException e) {
164: String msg = "Error creating default source XPath expression : "
165: + DEFAULT_XPATH;
166: log.error(msg, e);
167: throw new SynapseException(msg, e);
168: }
169: }
170:
171: /**
172: * Transforms this message (or its element specified as the source) using the
173: * given XSLT transformation
174: *
175: * @param synCtx the current message where the transformation will apply
176: * @return true always
177: */
178: public boolean mediate(MessageContext synCtx) {
179:
180: boolean traceOn = isTraceOn(synCtx);
181: boolean traceOrDebugOn = isTraceOrDebugOn(traceOn);
182:
183: if (traceOrDebugOn) {
184: traceOrDebug(traceOn, "Start : XSLT mediator");
185:
186: if (traceOn && trace.isTraceEnabled()) {
187: trace.trace("Message : " + synCtx.getEnvelope());
188: }
189: }
190:
191: try {
192: performXSLT(synCtx, traceOrDebugOn, traceOn);
193:
194: } catch (Exception e) {
195: handleException(
196: "Unable to perform XSLT transformation using : "
197: + xsltKey
198: + " against source XPath : "
199: + (sourceXPathString == null ? DEFAULT_XPATH
200: : " source XPath : "
201: + sourceXPathString), e,
202: synCtx);
203:
204: }
205:
206: if (traceOrDebugOn) {
207: traceOrDebug(traceOn, "End : XSLT mediator");
208: }
209:
210: return true;
211: }
212:
213: /**
214: * Perform actual XSLT transformation
215: * @param synCtx current message
216: * @param traceOrDebugOn is trace or debug on?
217: * @param traceOn is trace on?
218: */
219: private void performXSLT(MessageContext synCtx,
220: final boolean traceOrDebugOn, final boolean traceOn) {
221:
222: boolean reCreate = false;
223: OMNode sourceNode = getTransformSource(synCtx);
224: TemporaryData tempSourceData = null;
225: InputStream isForSource = null;
226: TemporaryData tempTargetData = null;
227: OutputStream osForTarget = null;
228: boolean isSoapEnvelope = (sourceNode == synCtx.getEnvelope());
229: boolean isSoapBody = (sourceNode == synCtx.getEnvelope()
230: .getBody());
231:
232: if (traceOrDebugOn) {
233: trace.trace("Transformation source : "
234: + sourceNode.toString());
235: }
236:
237: Source transformSrc = null;
238: Result transformTgt = null;
239:
240: if (useDOMSourceAndResults) {
241: if (traceOrDebugOn) {
242: traceOrDebug(traceOn,
243: "Using a DOMSource for transformation");
244: }
245:
246: // for fast transformations create a DOMSource - ** may not work always though **
247: transformSrc = new DOMSource(((Element) ElementHelper
248: .importOMElement((OMElement) sourceNode,
249: DOOMAbstractFactory.getOMFactory()))
250: .getOwnerDocument());
251: DocumentBuilderFactoryImpl.setDOOMRequired(true);
252:
253: try {
254: transformTgt = new DOMResult(DocumentBuilderFactoryImpl
255: .newInstance().newDocumentBuilder()
256: .newDocument());
257: } catch (ParserConfigurationException e) {
258: handleException(
259: "Error creating a DOMResult for the transformation,"
260: + " Consider setting optimization feature : "
261: + USE_DOM_SOURCE_AND_RESULTS + " off",
262: e, synCtx);
263: }
264:
265: } else {
266: if (traceOrDebugOn) {
267: traceOrDebug(traceOn,
268: "Using byte array serialization for transformation");
269: }
270:
271: try {
272: // Serialize the source node into a buffer or temporary file
273: tempSourceData = new TemporaryData(1024,
274: BYTE_ARRAY_SIZE / 1024, "xs_", ".xml");
275: OutputStream osForSource = tempSourceData
276: .getOutputStream();
277: try {
278: XMLStreamWriter xsWriterForSource = XMLOutputFactory
279: .newInstance().createXMLStreamWriter(
280: osForSource);
281:
282: sourceNode.serialize(xsWriterForSource);
283: } finally {
284: osForSource.close();
285: }
286: isForSource = tempSourceData.getInputStream();
287: transformSrc = new StreamSource(isForSource);
288:
289: } catch (XMLStreamException e) {
290: handleException(
291: "Error creating a StreamSource for the transformation",
292: e, synCtx);
293:
294: } catch (IOException e) {
295: handleException(
296: "I/O error while creating a StreamSource for the transformation",
297: e, synCtx);
298: }
299:
300: tempTargetData = new TemporaryData(1024,
301: BYTE_ARRAY_SIZE / 1024, "xt_", ".xml");
302: osForTarget = tempTargetData.getOutputStream();
303: transformTgt = new StreamResult(osForTarget);
304: }
305:
306: if (transformTgt == null) {
307: if (traceOrDebugOn) {
308: traceOrDebug(traceOn,
309: "Was unable to get a javax.xml.transform.Result created");
310: }
311: return;
312: }
313:
314: // build transformer - if necessary
315: Entry dp = synCtx.getConfiguration()
316: .getEntryDefinition(xsltKey);
317:
318: // if the xsltKey refers to a dynamic resource
319: if (dp != null && dp.isDynamic()) {
320: if (!dp.isCached() || dp.isExpired()) {
321: reCreate = true;
322: }
323: }
324:
325: synchronized (transformerLock) {
326: if (reCreate || cachedTemplates == null) {
327: try {
328: cachedTemplates = transFact
329: .newTemplates(SynapseConfigUtils
330: .getStreamSource(synCtx
331: .getEntry(xsltKey)));
332: if (cachedTemplates == null) {
333: handleException(
334: "Error compiling the XSLT with key : "
335: + xsltKey, synCtx);
336: }
337: } catch (Exception e) {
338: handleException(
339: "Error creating XSLT transformer using : "
340: + xsltKey, e, synCtx);
341: }
342: }
343: }
344:
345: try {
346: // perform transformation
347: Transformer transformer = cachedTemplates.newTransformer();
348: if (!properties.isEmpty()) {
349: // set the parameters which will pass to the Transformation
350: for (int i = 0; i < properties.size(); i++) {
351: MediatorProperty prop = (MediatorProperty) properties
352: .get(i);
353: if (prop != null) {
354: if (prop.getValue() != null) {
355: transformer.setParameter(prop.getName(),
356: prop.getValue());
357: } else {
358: transformer.setParameter(prop.getName(),
359: prop.getExpression()
360: .getStringValue(synCtx));
361: }
362: }
363: }
364: }
365:
366: transformer.setErrorListener(new ErrorListener() {
367: public void warning(TransformerException e)
368: throws TransformerException {
369: if (traceOrDebugOn) {
370: traceOrDebug(traceOn,
371: "Warning encountered during transformation : "
372: + e.getMessage());
373: }
374: log.warn("Transformation warning encountered", e);
375: }
376:
377: public void error(TransformerException e)
378: throws TransformerException {
379: setTransformerException(e);
380: }
381:
382: public void fatalError(TransformerException e)
383: throws TransformerException {
384: setTransformerException(e);
385: }
386: });
387: transformer.transform(transformSrc, transformTgt);
388:
389: if (transformerException != null) {
390: throw transformerException;
391: }
392:
393: if (traceOrDebugOn) {
394: traceOrDebug(traceOn,
395: "Transformation completed - processing result");
396: }
397:
398: if (isForSource != null) {
399: IOUtils.closeQuietly(isForSource);
400: isForSource = null;
401: }
402:
403: if (tempSourceData != null) {
404: tempSourceData.release();
405: tempSourceData = null;
406: }
407:
408: // get the result OMElement
409: OMElement result = null;
410: if (transformTgt instanceof DOMResult) {
411: Node node = ((DOMResult) transformTgt).getNode();
412: if (node == null) {
413: if (traceOrDebugOn) {
414: traceOrDebug(
415: traceOn,
416: ("Transformation result (DOMResult) was null"));
417: }
418: return;
419: }
420: Node resultNode = node.getFirstChild();
421: if (resultNode == null) {
422: if (traceOrDebugOn) {
423: traceOrDebug(
424: traceOn,
425: ("Transformation result (DOMResult) was empty"));
426: }
427: return;
428: }
429:
430: result = ElementHelper.importOMElement(
431: (OMElement) resultNode, OMAbstractFactory
432: .getOMFactory());
433:
434: } else {
435:
436: try {
437: XMLStreamReader reader = StAXUtils
438: .createXMLStreamReader(tempTargetData
439: .getInputStream());
440: if (isSoapEnvelope) {
441: result = new StAXSOAPModelBuilder(reader)
442: .getSOAPEnvelope();
443: } else {
444: result = new StAXOMBuilder(reader)
445: .getDocumentElement();
446: }
447:
448: } catch (XMLStreamException e) {
449: handleException(
450: "Error building result element from XSLT transformation",
451: e, synCtx);
452:
453: } catch (Exception e) {
454: result = handleNonXMLResult(tempTargetData,
455: traceOrDebugOn, traceOn);
456:
457: }
458:
459: }
460:
461: if (result == null) {
462: if (traceOrDebugOn) {
463: traceOrDebug(traceOn,
464: "Transformation result was null");
465: }
466: return;
467: } else {
468: if (traceOn && trace.isTraceEnabled()) {
469: trace.trace("Transformation result : "
470: + result.toString());
471: }
472: }
473:
474: if (targetPropertyName != null) {
475: // add result XML as a message context property to the message
476: if (traceOrDebugOn) {
477: traceOrDebug(traceOn,
478: "Adding result as message context property : "
479: + targetPropertyName);
480: }
481: synCtx.setProperty(targetPropertyName, result);
482: } else {
483: if (traceOrDebugOn) {
484: traceOrDebug(traceOn,
485: "Replace "
486: + (isSoapEnvelope ? "SOAP envelope"
487: : isSoapBody ? "SOAP body"
488: : "node")
489: + " with result");
490: }
491:
492: if (isSoapEnvelope) {
493: try {
494: synCtx.setEnvelope((SOAPEnvelope) result);
495: } catch (AxisFault ex) {
496: handleException(
497: "Unable to replace SOAP envelope with result",
498: ex, synCtx);
499: }
500:
501: } else if (isSoapBody) {
502: for (Iterator iter = synCtx.getEnvelope().getBody()
503: .getChildElements(); iter.hasNext();) {
504: OMElement child = (OMElement) iter.next();
505: child.detach();
506: }
507:
508: for (Iterator iter = result.getChildElements(); iter
509: .hasNext();) {
510: OMElement child = (OMElement) iter.next();
511: synCtx.getEnvelope().getBody().addChild(child);
512: }
513:
514: } else {
515: sourceNode.insertSiblingAfter(result);
516: sourceNode.detach();
517: }
518: }
519:
520: } catch (TransformerException e) {
521: handleException(
522: "Error performing XSLT transformation using : "
523: + xsltKey, e, synCtx);
524: }
525: }
526:
527: /**
528: * Return the OMNode to be used for the transformation. If a source XPath is not specified,
529: * this will default to the first child of the SOAP body i.e. - //*:Envelope/*:Body/child::*
530: *
531: * @param synCtx the message context
532: * @return the OMNode against which the transformation should be performed
533: */
534: private OMNode getTransformSource(MessageContext synCtx) {
535:
536: try {
537: Object o = source.evaluate(synCtx);
538: if (o instanceof OMNode) {
539: return (OMNode) o;
540: } else if (o instanceof List && !((List) o).isEmpty()) {
541: return (OMNode) ((List) o).get(0); // Always fetches *only* the first
542: } else {
543: handleException(
544: "The evaluation of the XPath expression "
545: + source
546: + " did not result in an OMNode",
547: synCtx);
548: }
549: } catch (JaxenException e) {
550: handleException("Error evaluating XPath expression : "
551: + source, e, synCtx);
552: }
553: return null;
554: }
555:
556: public SynapseXPath getSource() {
557: return source;
558: }
559:
560: public void setSource(SynapseXPath source) {
561: this .source = source;
562: }
563:
564: public String getXsltKey() {
565: return xsltKey;
566: }
567:
568: public void setXsltKey(String xsltKey) {
569: this .xsltKey = xsltKey;
570: }
571:
572: public void addProperty(MediatorProperty p) {
573: properties.add(p);
574: }
575:
576: /**
577: * to add a feature which need to set to the TransformerFactory
578: * @param featureName The name of the feature
579: * @param isFeatureEnable should this feature enable?
580: */
581:
582: public void addFeature(String featureName, boolean isFeatureEnable) {
583: try {
584: MediatorProperty mp = new MediatorProperty();
585: mp.setName(featureName);
586: if (isFeatureEnable) {
587: mp.setValue("true");
588: } else {
589: mp.setValue("false");
590: }
591: explicitFeatures.add(mp);
592: if (USE_DOM_SOURCE_AND_RESULTS.equals(featureName)) {
593: useDOMSourceAndResults = isFeatureEnable;
594: } else {
595: transFact.setFeature(featureName, isFeatureEnable);
596: }
597: } catch (TransformerConfigurationException e) {
598: String msg = "Error occured when setting features to the TransformerFactory";
599: log.error(msg, e);
600: throw new SynapseException(msg, e);
601: }
602: }
603:
604: /**
605: * If the transformation results in a non-XML payload, use standard wrapper elements
606: * to wrap the text payload so that other mediators could still process the result
607: * @param tempData the text payload
608: * @param traceOrDebugOn is tracing on debug logging on?
609: * @param traceOn is tracing on?
610: * @return an OMElement wrapping the text payload
611: */
612: private OMElement handleNonXMLResult(TemporaryData tempData,
613: boolean traceOrDebugOn, boolean traceOn) {
614:
615: OMFactory fac = OMAbstractFactory.getOMFactory();
616: OMElement wrapper = null;
617:
618: if (traceOrDebugOn) {
619: traceOrDebug(traceOn,
620: "Processing non SOAP/XML (text) transformation result");
621: }
622: if (traceOn && trace.isTraceEnabled()) {
623: trace.trace("Wrapping text transformation result");
624: }
625:
626: if (tempData != null) {
627: TextFileDataSource txtFileDS = new TextFileDataSource(
628: tempData);
629: wrapper = new OMSourcedElementImpl(
630: BaseConstants.DEFAULT_TEXT_WRAPPER, fac, txtFileDS);
631: }
632:
633: return wrapper;
634: }
635:
636: /**
637: *
638: * @return Returns the features explicitly set to the TransformerFactory through this mediator
639: */
640: public List getFeatures() {
641: return explicitFeatures;
642: }
643:
644: public void addAllProperties(List list) {
645: properties.addAll(list);
646: }
647:
648: public List getProperties() {
649: return properties;
650: }
651:
652: public void setSourceXPathString(String sourceXPathString) {
653: this .sourceXPathString = sourceXPathString;
654: }
655:
656: public String getTargetPropertyName() {
657: return targetPropertyName;
658: }
659:
660: public void setTargetPropertyName(String targetPropertyName) {
661: this .targetPropertyName = targetPropertyName;
662: }
663:
664: public void setTransformerException(
665: TransformerException transformerException) {
666: this.transformerException = transformerException;
667: }
668: }
|