Package org.apache.synapse.mediators.transform

Source Code of org.apache.synapse.mediators.transform.XSLTMediator

/*
*  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.transform;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axiom.om.impl.dom.DOOMAbstractFactory;
import org.apache.axiom.om.impl.dom.jaxp.DocumentBuilderFactoryImpl;
import org.apache.axiom.om.impl.llom.OMSourcedElementImpl;
import org.apache.axiom.om.impl.llom.OMTextImpl;
import org.apache.axiom.om.util.ElementHelper;
import org.apache.axiom.om.util.StAXUtils;
import org.apache.axiom.om.xpath.AXIOMXPath;
import org.apache.axiom.soap.SOAP11Constants;
import org.apache.axiom.soap.SOAP12Constants;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.impl.builder.StAXSOAPModelBuilder;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseException;
import org.apache.synapse.config.Entry;
import org.apache.synapse.config.SynapseConfigUtils;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.mediators.AbstractMediator;
import org.apache.synapse.mediators.MediatorProperty;
import org.apache.synapse.transport.base.BaseConstants;
import org.apache.synapse.util.FixedByteArrayOutputStream;
import org.apache.synapse.util.TextFileDataSource;
import org.apache.axis2.AxisFault;
import org.jaxen.JaxenException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import javax.activation.FileDataSource;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;

/**
* The XSLT mediator performs an XSLT transformation requested, using
* the current message. The source attribute (if available) specifies the source element
* on which the transformation would be applied. It will default to the first child of
* the messages' SOAP body, if it is omitted.
*
* Additional properties passed into this mediator would become parameters for XSLT.
* Additional features passed into this mediator would become features except for
* "http://ws.apache.org/ns/synapse/transform/feature/dom" for the Transformer Factory, which
* is used to decide between using DOM and Streams during the transformation process. By default
* this is turned on as an optimization, but should be set to false if issues are detected
*
*  Note: Set the TransformerFactory system property to generate and use translets
*  -Djavax.xml.transform.TransformerFactory=org.apache.xalan.xsltc.trax.TransformerFactoryImpl
*
*/
public class XSLTMediator extends AbstractMediator {

    /** Maximum size of a byte array stream attempted in-memory before file serialization is used */
    private static final int BYTE_ARRAY_SIZE = 8192;
    /**
     * The feature for which deciding swiching between DOM and Stream during the
     * transformation process
     */
    public static final String USE_DOM_SOURCE_AND_RESULTS =
        "http://ws.apache.org/ns/synapse/transform/feature/dom";
    /**
     * The resource key/name which refers to the XSLT to be used for the transformation
     */
    private String xsltKey = null;

    /** Variable to hold source XPath string to use for debugging */
    private String sourceXPathString = null;

    /**
     * The (optional) XPath expression which yields the source element for a transformation
     */
    private AXIOMXPath source = null;

    /**
     * The name of the message context property to store the transformation result 
     */
    private String targetPropertyName = null;

    /**
     * Any parameters which should be passed into the XSLT transformation
     */
    private List properties = new ArrayList();

    /**
     * Any features which should be set to the TransformerFactory by explicitly
     */
    private List explicitFeatures = new ArrayList();

    /**
     * The Template instance used to create a Transformer object. This is  thread-safe
     *
     * @see javax.xml.transform.Templates
     */
    private Templates cachedTemplates = null;

    /**
     * The TransformerFactory instance which use to create Templates...This is not thread-safe.
     * @see javax.xml.transform.TransformerFactory
     */
    private final TransformerFactory transFact = TransformerFactory.newInstance();

    /**
     * Lock used to ensure thread-safe creation and use of the above Transformer
     */
    private final Object transformerLock = new Object();

    /**
     *  Is it need to use DOMSource and DOMResult?
     */
    private boolean useDOMSourceAndResults = false;

    // todo - this is a hack to get the handler module case working - ruwan
    //    public static final String DEFAULT_XPATH = "//s11:Envelope/s11:Body/child::*[position()=1] | " +
    //            "//s12:Envelope/s12:Body/child::*[position()=1]";
    public static final String DEFAULT_XPATH = "s11:Body/child::*[position()=1] | " +
            "s12:Body/child::*[position()=1]";

    public XSLTMediator() {
        // create the default XPath
        try {
            this.source = new AXIOMXPath(DEFAULT_XPATH);
            this.source.addNamespace("s11", SOAP11Constants.SOAP_ENVELOPE_NAMESPACE_URI);
            this.source.addNamespace("s12", SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI);
        } catch (JaxenException e) {
            String msg = "Error creating default source XPath expression : " + DEFAULT_XPATH;
            log.error(msg, e);
            throw new SynapseException(msg, e);
        }
    }

    /**
     * Transforms this message (or its element specified as the source) using the
     * given XSLT transformation
     *
     * @param synCtx the current message where the transformation will apply
     * @return true always
     */
    public boolean mediate(MessageContext synCtx) {

        boolean traceOn = isTraceOn(synCtx);
        boolean traceOrDebugOn = isTraceOrDebugOn(traceOn);

        if (traceOrDebugOn) {
            traceOrDebug(traceOn, "Start : XSLT mediator");

            if (traceOn && trace.isTraceEnabled()) {
                trace.trace("Message : " + synCtx.getEnvelope());
            }
        }

        try {
            performXSLT(synCtx, traceOrDebugOn, traceOn);

        } catch (Exception e) {
            handleException("Unable to perform XSLT transformation using : " + xsltKey +
                " against source XPath : " +
                (sourceXPathString == null ? DEFAULT_XPATH : " source XPath : " +
                 sourceXPathString), e, synCtx);

        }

        if (traceOrDebugOn) {
            traceOrDebug(traceOn, "End : XSLT mediator");
        }

        return true;
    }

    /**
     * Perform actual XSLT transformation
     * @param synCtx current message
     * @param traceOrDebugOn is trace or debug on?
     * @param traceOn is trace on?
     */
    private void performXSLT(MessageContext synCtx, boolean traceOrDebugOn, boolean traceOn) {

        boolean reCreate = false;
        OMNode sourceNode = getTransformSource(synCtx);
        OutputStream osForTarget = null;
        InputStream  isForSource = null;
        ByteArrayOutputStream baosForTarget = new FixedByteArrayOutputStream(BYTE_ARRAY_SIZE);
        File tempTargetFile = null;
        File tempSourceFile = null;
        boolean isSoapEnvelope = (sourceNode == synCtx.getEnvelope());
        boolean isSoapBody = (sourceNode == synCtx.getEnvelope().getBody());

        if (traceOrDebugOn) {
            trace.trace("Transformation source : " + sourceNode.toString());
        }

        Source transformSrc = null;
        Result transformTgt = null;

        if (useDOMSourceAndResults) {
            if (traceOrDebugOn) {
                traceOrDebug(traceOn, "Using a DOMSource for transformation");
            }

            // for fast transformations create a DOMSource - ** may not work always though **
            transformSrc = new DOMSource(
                ((Element) ElementHelper.importOMElement((OMElement) sourceNode,
                DOOMAbstractFactory.getOMFactory())).getOwnerDocument());
            DocumentBuilderFactoryImpl.setDOOMRequired(true);

            try {
                transformTgt = new DOMResult(
                    DocumentBuilderFactoryImpl.newInstance().newDocumentBuilder().newDocument());
            } catch (ParserConfigurationException e) {
                handleException("Error creating a DOMResult for the transformation," +
                    " Consider setting optimization feature : " + USE_DOM_SOURCE_AND_RESULTS +
                    " off", e, synCtx);
            }

        } else {
            if (traceOrDebugOn) {
                traceOrDebug(traceOn, "Using byte array serialization for transformation");
            }

            try {
                // create a byte array output stream and serialize the source node into it
                ByteArrayOutputStream baosForSource = new FixedByteArrayOutputStream(BYTE_ARRAY_SIZE);
                XMLStreamWriter xsWriterForSource = XMLOutputFactory.newInstance().
                    createXMLStreamWriter(baosForSource);

                sourceNode.serialize(xsWriterForSource);
                isForSource = new ByteArrayInputStream(baosForSource.toByteArray());
                transformSrc = new StreamSource(isForSource);
                transformTgt = new StreamResult(baosForTarget);

            } catch (XMLStreamException e) {
                handleException("Error creating a StreamResult for the transformation", e, synCtx);

            } catch (SynapseException x) {

                if (traceOrDebugOn) {
                    traceOrDebug(traceOn, "Error creating a StreamResult using a byte array" +
                        " - attempting using temporary files for serialization");
                }

                OutputStream osForSource = null;

                try {
                    // create a output stream and serialize the source node into it
                    tempSourceFile = File.createTempFile("xs_", ".xml");
                    tempTargetFile = File.createTempFile("xt_", ".xml");

                    osForSource = new FileOutputStream(tempSourceFile);
                    osForTarget = new FileOutputStream(tempTargetFile);

                    XMLStreamWriter xsWriterForSource =
                        XMLOutputFactory.newInstance().createXMLStreamWriter(osForSource);

                    sourceNode.serialize(xsWriterForSource);
                    transformSrc = new StreamSource(tempSourceFile);
                    transformTgt = new StreamResult(osForTarget);

                } catch (XMLStreamException e) {
                    handleException("Error creating a StreamResult for the transformation", e, synCtx);
                } catch (IOException e) {
                    handleException("Error using a temporary file/s for the transformation", e, synCtx);
                } finally {
                    try {
                        osForSource.close();
                    } catch (IOException ignore) {}
                }
            }
        }

        if (transformTgt == null) {
            if (traceOrDebugOn) {
                traceOrDebug(traceOn, "Was unable to get a javax.xml.transform.Result created");
            }
            return;
        }

        // build transformer - if necessary
        Entry dp = synCtx.getConfiguration().getEntryDefinition(xsltKey);

        // if the xsltKey refers to a dynamic resource
        if (dp != null && dp.isDynamic()) {
            if (!dp.isCached() || dp.isExpired()) {
                reCreate = true;
            }
        }

        synchronized (transformerLock) {
            if (reCreate || cachedTemplates == null) {
                try {
                    cachedTemplates = transFact.newTemplates(
                        SynapseConfigUtils.getStreamSource(synCtx.getEntry(xsltKey)));
                    if (cachedTemplates == null) {
                        handleException("Error compiling the XSLT with key : " + xsltKey, synCtx);
                    }
                } catch (Exception e) {
                    handleException("Error creating XSLT transformer using : "
                        + xsltKey, e, synCtx);
                }
            }
        }

        try {
            // perform transformation
            Transformer transformer = cachedTemplates.newTransformer();
            if (!properties.isEmpty()) {
                // set the parameters which will pass to the Transformation
                for (int i = 0; i < properties.size(); i++) {
                    MediatorProperty prop = (MediatorProperty) properties.get(i);
                    if (prop != null) {
                        if (prop.getValue() != null) {
                            transformer.setParameter(prop.getName(), prop.getValue());
                        } else {
                            transformer.setParameter(prop.getName(),
                                Axis2MessageContext.getStringValue(prop.getExpression(), synCtx));
                        }
                    }
                }
            }

            try {
                transformer.transform(transformSrc, transformTgt);

            } catch (TransformerException x) {
                // did we exceed the in-memory BYTE_ARRAY_SIZE? if so, use a file for output
                try {
                    tempTargetFile = File.createTempFile("xt_", ".xml");
                    osForTarget  = new FileOutputStream(tempTargetFile);
                    transformTgt = new StreamResult(osForTarget);

                    // retry transformation again
                    isForSource.reset();
                    transformer.reset();
                    transformer.transform(transformSrc, transformTgt);

                } catch (IOException e) {
                    handleException("Error using a temporary file/s for the transformation", e, synCtx);
                }
            }

            if (traceOrDebugOn) {
                traceOrDebug(traceOn, "Transformation completed - processing result");
            }

            if (tempSourceFile != null) {
                boolean deleted = tempSourceFile.delete();
                if (!deleted) {
                    tempSourceFile.deleteOnExit();
                }
            }

            // get the result OMElement
            OMElement result = null;
            if (transformTgt instanceof DOMResult) {
                Node node = ((DOMResult) transformTgt).getNode();
                if (node == null) {
                    if (traceOrDebugOn) {
                        traceOrDebug(traceOn, ("Transformation result (DOMResult) was null"));
                    }
                    return;
                }
                Node resultNode = node.getFirstChild();
                if (resultNode == null) {
                    if (traceOrDebugOn) {
                        traceOrDebug(traceOn, ("Transformation result (DOMResult) was empty"));
                    }
                    return;
                }

                result = ElementHelper.importOMElement(
                    (OMElement) resultNode, OMAbstractFactory.getOMFactory());

            } else {

                // if we used a temporary file for the output of the transformation, read from it
                if (tempTargetFile != null) {
                    try {
                        XMLStreamReader reader = StAXUtils.createXMLStreamReader(
                            new FileInputStream(tempTargetFile));
                        if (isSoapEnvelope) {
                            result = new StAXSOAPModelBuilder(reader).getSOAPEnvelope();
                        } else {
                            result = new StAXOMBuilder(reader).getDocumentElement();
                        }                       

                    } catch (XMLStreamException e) {
                        handleException(
                            "Error building result element from XSLT transformation", e, synCtx);

                    } catch (Exception e) {
                        result = handleNonXMLResult(tempTargetFile, traceOrDebugOn, traceOn);

                    } finally {
                        boolean deleted = tempTargetFile.delete();
                        if (!deleted) {
                            tempTargetFile.deleteOnExit();
                        }
                    }

                } else {
                    // read the Fixed byte array stream
                    try {
                        XMLStreamReader reader = StAXUtils.createXMLStreamReader(
                            new ByteArrayInputStream(baosForTarget.toByteArray()));
                        if (isSoapEnvelope) {
                            result = new StAXSOAPModelBuilder(reader).getSOAPEnvelope();
                        } else {
                            result = new StAXOMBuilder(reader).getDocumentElement();
                        }

                    } catch (XMLStreamException e) {
                        handleException(
                            "Error building result element from XSLT transformation", e, synCtx);

                    } catch (Exception e) {
                        result = handleNonXMLResult(baosForTarget.toString(), traceOrDebugOn, traceOn);
                    }
                }
            }

            if (result == null) {
                if (traceOrDebugOn) {
                    traceOrDebug(traceOn, "Transformation result was null");
                }
                return;
            } else {
                if (traceOn && trace.isTraceEnabled()) {
                    trace.trace("Transformation result : " + result.toString());
                }
            }

            if (targetPropertyName != null) {
                // add result XML as a message context property to the message
                if (traceOrDebugOn) {
                    traceOrDebug(traceOn, "Adding result as message context property : " +
                        targetPropertyName);
                }
                synCtx.setProperty(targetPropertyName, result);
            } else {
                if (traceOrDebugOn) {
                    traceOrDebug(traceOn, "Replace " +
                        (isSoapEnvelope ? "SOAP envelope" : isSoapBody ? "SOAP body" : "node")
                        + " with result");
                }

                if (isSoapEnvelope) {
                    try {
                        synCtx.setEnvelope((SOAPEnvelope) result);
                    } catch (AxisFault ex) {
                        handleException("Unable to replace SOAP envelope with result", ex, synCtx);
                    }

                } else if (isSoapBody) {
                    for (Iterator iter = synCtx.getEnvelope().getBody().getChildElements();
                        iter.hasNext(); ) {
                        OMElement child = (OMElement) iter.next();
                        child.detach();
                    }

                    for (Iterator iter = result.getChildElements(); iter.hasNext(); ) {
                        OMElement child = (OMElement) iter.next();
                        synCtx.getEnvelope().getBody().addChild(child);
                    }

                } else {
                    sourceNode.insertSiblingAfter(result);
                    sourceNode.detach();
                }
            }

        } catch (TransformerException e) {
            handleException("Error performing XSLT transformation using : " + xsltKey, e, synCtx);
        }
    }

    /**
     * Return the OMNode to be used for the transformation. If a source XPath is not specified,
     * this will default to the first child of the SOAP body i.e. - //*:Envelope/*:Body/child::*
     *
     * @param synCtx the message context
     * @return the OMNode against which the transformation should be performed
     */
    private OMNode getTransformSource(MessageContext synCtx) {

        try {
            Object o = source.evaluate(synCtx.getEnvelope());
            if (o instanceof OMNode) {
                return (OMNode) o;
            } else if (o instanceof List && !((List) o).isEmpty()) {
                return (OMNode) ((List) o).get(0)// Always fetches *only* the first
            } else {
                handleException("The evaluation of the XPath expression "
                        + source + " did not result in an OMNode", synCtx);
            }
        } catch (JaxenException e) {
            handleException("Error evaluating XPath expression : " + source, e, synCtx);
        }
        return null;
    }

    public AXIOMXPath getSource() {
        return source;
    }

    public void setSource(AXIOMXPath source) {
        this.source = source;
    }

    public String getXsltKey() {
        return xsltKey;
    }

    public void setXsltKey(String xsltKey) {
        this.xsltKey = xsltKey;
    }

    public void addProperty(MediatorProperty p) {
        properties.add(p);
    }
   
    /**
     * to add a feature which need to set to the TransformerFactory
     * @param  featureName The name of the feature
     * @param isFeatureEnable should this feature enable?
     */
   
    public void addFeature(String featureName, boolean isFeatureEnable) {
        try {
            MediatorProperty mp = new MediatorProperty();
            mp.setName(featureName);
            if (isFeatureEnable) {
                mp.setValue("true");
            } else {
                mp.setValue("false");
            }
            explicitFeatures.add(mp);
            if (USE_DOM_SOURCE_AND_RESULTS.equals(featureName)) {
                useDOMSourceAndResults = isFeatureEnable;
            } else {
                transFact.setFeature(featureName, isFeatureEnable);
            }
        } catch (TransformerConfigurationException e) {
            String msg = "Error occured when setting features to the TransformerFactory";
            log.error(msg, e);
            throw new SynapseException(msg, e);
        }
    }

    /**
     * If the transformation results in a non-XML payload, use standard wrapper elements
     * to wrap the text payload so that other mediators could still process the result
     * @param file the text payload file
     * @param traceOrDebugOn is tracing on debug logging on?
     * @param traceOn is tracing on?
     * @return an OMElement wrapping the text payload
     */
    private OMElement handleNonXMLResult(File file, boolean traceOrDebugOn, boolean traceOn) {

        OMFactory fac = OMAbstractFactory.getOMFactory();
        OMElement wrapper = null;

        if (traceOrDebugOn) {
            traceOrDebug(traceOn, "Processing non SOAP/XML (text) transformation result");
        }
        if (traceOn && trace.isTraceEnabled()) {
            trace.trace("Wrapping text transformation result from : " + file);
        }

        if (file != null) {
            TextFileDataSource txtFileDS = new TextFileDataSource(new FileDataSource(file));
            wrapper = new OMSourcedElementImpl(BaseConstants.DEFAULT_TEXT_WRAPPER, fac, txtFileDS);
        }

        return wrapper;
    }

    /**
     * If the transformation results in a non-XML payload, use standard wrapper elements
     * to wrap the text payload so that other mediators could still process the result
     * @param textPayload the text payload to wrap
     * @param traceOrDebugOn is tracing on debug logging on?
     * @param traceOn is tracing on?
     * @return an OMElement wrapping the text payload
     */
    private OMElement handleNonXMLResult(String textPayload, boolean traceOrDebugOn, boolean traceOn) {

        OMFactory fac = OMAbstractFactory.getOMFactory();
        OMElement wrapper = null;

        if (traceOrDebugOn) {
            traceOrDebug(traceOn, "Processing non SOAP/XML (text) transformation result");
        }
        if (traceOn && trace.isTraceEnabled()) {
            trace.trace("Wrapping text transformation result : " + textPayload);
        }

        if (textPayload != null) {
            OMTextImpl textData = (OMTextImpl) fac.createOMText(textPayload);
            wrapper = fac.createOMElement(BaseConstants.DEFAULT_TEXT_WRAPPER, null);
            wrapper.addChild(textData);
        }

        return wrapper;
    }

    /**
     *
     * @return Returns the features explicitly  set to the TransformerFactory through this mediator
     */
    public List getFeatures(){
        return explicitFeatures;
    }

    public void addAllProperties(List list) {
        properties.addAll(list);
    }

    public List getProperties() {
        return properties;
    }

    public void setSourceXPathString(String sourceXPathString) {
        this.sourceXPathString = sourceXPathString;
    }

    public String getTargetPropertyName() {
        return targetPropertyName;
    }

    public void setTargetPropertyName(String targetPropertyName) {
        this.targetPropertyName = targetPropertyName;
    }
}

  
TOP

Related Classes of org.apache.synapse.mediators.transform.XSLTMediator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.