package org.wso2.carbon.mediator.fastXSLT;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.OMNode;
import org.apache.axiom.om.OMText;
import org.apache.axiom.om.util.AXIOMUtil;
import org.apache.axiom.soap.SOAPBody;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.commons.io.IOUtils;
import org.apache.synapse.ManagedLifecycle;
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.core.SynapseEnvironment;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.mediators.AbstractMediator;
import org.apache.synapse.mediators.MediatorProperty;
import org.apache.synapse.mediators.Value;
import org.apache.synapse.transport.passthru.PassThroughConstants;
import org.apache.synapse.transport.passthru.Pipe;
import org.apache.synapse.transport.passthru.config.PassThroughConfiguration;
import org.apache.synapse.transport.passthru.util.RelayConstants;
import org.apache.synapse.transport.passthru.util.RelayUtils;
import org.apache.synapse.util.jaxp.DOOMResultBuilderFactory;
import org.apache.synapse.util.jaxp.DOOMSourceBuilderFactory;
import org.apache.synapse.util.jaxp.ResultBuilderFactory;
import org.apache.synapse.util.jaxp.SourceBuilderFactory;
import org.apache.synapse.util.jaxp.StreamResultBuilderFactory;
import org.apache.synapse.util.jaxp.StreamSourceBuilderFactory;
import org.apache.synapse.util.resolver.ResourceMap;
import org.apache.synapse.util.xpath.SourceXPathSupport;
import org.apache.synapse.util.xpath.SynapseXPath;
import org.springframework.util.xml.StaxUtils;
import org.wso2.carbon.relay.StreamingOnRequestDataSource;
public class FastXSLTMediator extends AbstractMediator implements ManagedLifecycle {
/**
* The feature for which deciding switching 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 name of the attribute that allows to specify the {@link SourceBuilderFactory}.
*/
public static final String SOURCE_BUILDER_FACTORY =
"http://ws.apache.org/ns/synapse/transform/attribute/sbf";
/**
* The name of the attribute that allows to specify the {@link ResultBuilderFactory}.
*/
public static final String RESULT_BUILDER_FACTORY =
"http://ws.apache.org/ns/synapse/transform/attribute/rbf";
private Value xsltKey=null;
private final Object transformerLock = new Object();
private Map<String, Templates> cachedTemplatesMap = new Hashtable<String, Templates>();
private TransformerFactory transFact = TransformerFactory.newInstance();
/**
* The (optional) XPath expression which yields the source element for a transformation
*/
private final SourceXPathSupport source = new SourceXPathSupport();
/**
* The name of the message context property to store the transformation result
*/
private String targetPropertyName = null;
/**
* A resource map used to resolve xsl:import and xsl:include.
*/
private ResourceMap resourceMap;
/**
* Any parameters which should be passed into the XSLT transformation
*/
private final List<MediatorProperty> properties = new ArrayList<MediatorProperty>();
/**
* The source builder factory to use.
*/
private SourceBuilderFactory sourceBuilderFactory = new StreamSourceBuilderFactory();
/**
* The result builder factory to use.
*/
private ResultBuilderFactory resultBuilderFactory = new StreamResultBuilderFactory();
/**
* Any features which should be set to the TransformerFactory explicitly
*/
private final List<MediatorProperty> transformerFactoryFeatures = new ArrayList<MediatorProperty>();
/**
* Any attributes which should be set to the TransformerFactory explicitly
*/
private final List<MediatorProperty> transformerFactoryAttributes
= new ArrayList<MediatorProperty>();
private int bufferSizeSupport = 1024*8;
public boolean mediate(MessageContext context) {
if (context.getEnvironment().isDebuggerEnabled()) {
if (super.divertMediationRoute(context)) {
return true;
}
}
InputStream inMessage = null;
Templates cTemplate = null;
org.apache.axis2.context.MessageContext axis2MC =null;
SynapseLog synLog = getLog(context);
if (synLog.isTraceOrDebugEnabled()) {
synLog.traceOrDebug("XMLConverter mediator : start");
}
try {
axis2MC = ((Axis2MessageContext)context).getAxis2MessageContext();
Pipe pipe= (Pipe) axis2MC.getProperty(PassThroughConstants.PASS_THROUGH_PIPE);
if(pipe != null){
inMessage = getMessageInputStreamFromSoapEnvelop(context);
if(inMessage == null ) {
inMessage = getMessageInputStreamPT(axis2MC, pipe);
}
} else{
inMessage = getMessageInputStreamBinaryRelay(context);
}
if( inMessage == null){
inMessage = getMessageInputStreamFromSoapEnvelop(context);
}
} catch (IOException e) {
handleException("Error while reading the input stream ", e, context);
}
String generatedXsltKey = xsltKey.evaluateValue(context);
// determine if it is needed to create or create the template
if (isCreationOrRecreationRequired(context)) {
// many threads can see this and come here for acquiring the lock
synchronized (transformerLock) {
// only first thread should create the template
if (isCreationOrRecreationRequired(context)) {
cTemplate = createTemplate(context, generatedXsltKey);
} else {
cTemplate = cachedTemplatesMap.get(generatedXsltKey);
}
}
} else {
//If already cached template then load it from cachedTemplatesMap
synchronized (transformerLock) {
cTemplate = cachedTemplatesMap.get(generatedXsltKey);
}
}
System.setProperty("javax.xml.transform.TransformerFactory", "net.sf.saxon.TransformerFactoryImpl");
try {
ByteArrayOutputStream _transformedOutMessage = new ByteArrayOutputStream();
transform(inMessage, _transformedOutMessage, cTemplate);
ByteArrayOutputStream _transformedOutMessageNew = new ByteArrayOutputStream();
IOUtils.write(_transformedOutMessage.toByteArray(), _transformedOutMessageNew);
BufferedInputStream bufferedStream = new BufferedInputStream(new ByteArrayInputStream(_transformedOutMessageNew.toByteArray()));
Pipe pipe= (Pipe) axis2MC.getProperty(PassThroughConstants.PASS_THROUGH_PIPE);
if(pipe != null) {
OutputStream msgContextOutStream = pipe.resetOutputStream();
axis2MC.setProperty(PassThroughConstants.BUFFERED_INPUT_STREAM, bufferedStream);
boolean fullLenthDone = false;
if (_transformedOutMessage.toByteArray().length > bufferSizeSupport) {
RelayUtils.builldMessage(axis2MC, false, bufferedStream);
fullLenthDone = true;
}
if (!fullLenthDone && Boolean.TRUE.equals(axis2MC.getProperty(PassThroughConstants.MESSAGE_BUILDER_INVOKED))) {
RelayUtils.builldMessage(axis2MC, false, bufferedStream);
} else if (!fullLenthDone) {
IOUtils.write(_transformedOutMessage.toByteArray(), msgContextOutStream);
pipe.setRawSerializationComplete(true);
}
} else {
OMElement omElement = context.getEnvelope().getBody().getFirstElement();
if (omElement != null) {
omElement.detach();
}
String omString = _transformedOutMessage.toString();
OMElement responseOM = AXIOMUtil.stringToOM(omString);
context.getEnvelope().getBody().addChild(responseOM);
}
//letting pipe know that the raw serialization has been completed and if reach pipe consume operation
//by looking at this variable the the pipe consumer operation encoder will completes after
//writing the byte stream to the output channel
if (synLog.isTraceOrDebugEnabled()) {
synLog.traceOrDebug("XMLConverter mediator : Done");
}
return true;
} catch (Exception e) {
handleException("Error while transforming the Stream ", e, context);
}
handleException("Unexpected SOAP message content found. " + this.getClass().getName() +
" mediator can only be used with messages built with BinaryRelayBuilder", context);
return false;
}
private void transform(InputStream xmlIn, OutputStream out, Templates templates)
throws Exception {
Transformer trans = templates.newTransformer();
Source source = new StreamSource(xmlIn);
Result resultXML = new StreamResult(out);
trans.transform(source, resultXML);
}
/**
* Create a XSLT template object and assign it to the cachedTemplates variable
*
* @param synCtx current message
* @param xsltKey evaluated xslt key(real key value) for dynamic or static key
* @return cached template
*/
private Templates createTemplate(MessageContext synCtx, String xsltKey) {
// Assign created template
Templates cachedTemplates = null;
try {
cachedTemplates = transFact.newTemplates(
SynapseConfigUtils.getStreamSource(synCtx.getEntry(xsltKey)));
if (cachedTemplates == null) {
// if cached template creation failed
handleException("Error compiling the XSLT with key : " + xsltKey, synCtx);
} else {
// if cached template is created then put it in to cachedTemplatesMap
cachedTemplatesMap.put(xsltKey, cachedTemplates);
}
} catch (Exception e) {
handleException("Error creating XSLT transformer using : " + xsltKey, e, synCtx);
}
return cachedTemplates;
}
private InputStream getMessageInputStreamBinaryRelay(MessageContext context) throws IOException {
InputStream temp;
SOAPEnvelope envelope = context.getEnvelope();
OMElement contentEle = envelope.getBody().getFirstElement();
if (contentEle != null) {
OMNode node = contentEle.getFirstOMChild();
if (node != null && (node instanceof OMText)) {
OMText binaryDataNode = (OMText) node;
DataHandler dh = (DataHandler) binaryDataNode.getDataHandler();
DataSource dataSource = dh.getDataSource();
if (dataSource instanceof StreamingOnRequestDataSource) {
// preserve the content while reading the incoming data stream
((StreamingOnRequestDataSource) dataSource).setLastUse(false);
// forcing to consume the incoming data stream
temp = dataSource.getInputStream();
return temp;
}
}
}
return null;
}
private InputStream getMessageInputStreamFromSoapEnvelop(MessageContext messageContext) {
InputStream temp = null;
SOAPEnvelope envelope = messageContext.getEnvelope();
OMElement contentEle = envelope.getBody().getFirstElement();
if (contentEle != null) {
String omElement = contentEle.toString();
temp = IOUtils.toInputStream(omElement);
}
return temp;
}
private InputStream getMessageInputStreamPT(org.apache.axis2.context.MessageContext context,Pipe pipe) throws IOException {
//AtomicBoolean inBufferInputMode = new AtomicBoole
if (pipe != null && Boolean.TRUE.equals(context.getProperty(PassThroughConstants.MESSAGE_BUILDER_INVOKED)) && context.getProperty(PassThroughConstants.BUFFERED_INPUT_STREAM) != null){
BufferedInputStream bufferedInputStream= (BufferedInputStream) context.getProperty(PassThroughConstants.BUFFERED_INPUT_STREAM);
bufferedInputStream.reset();
bufferedInputStream.mark(0);
return bufferedInputStream;
}
if(pipe != null ){
return pipe.getInputStream();
}
return null;
}
private MessageDataSource getResultBlob(MessageContext synCtx) throws IOException {
SynapseEnvironment synEnv = synCtx.getEnvironment();
return new MessageDataSource(synEnv.createOverflowBlob());
}
private boolean writeResult(MessageContext synCtx, MessageDataSource resulMessage) throws IOException, XMLStreamException {
if (resulMessage != null) {
SOAPFactory factory = OMAbstractFactory.getSOAP12Factory();
QName BINARY_CONTENT_QNAME = new QName("http://ws.apache.org/commons/ns/payload", "binary");
OMNamespace ns = factory.createOMNamespace(
BINARY_CONTENT_QNAME.getNamespaceURI(), "ns");
OMElement omEle = factory.createOMElement(
BINARY_CONTENT_QNAME.getLocalPart(), ns);
DataHandler dataHandler = new DataHandler(resulMessage);
OMText textData = factory.createOMText(dataHandler, true);
omEle.addChild(textData);
SOAPBody body = synCtx.getEnvelope().getBody();
if(body.getFirstElement() != null){
body.getFirstElement().detach();
}
body.addChild(omEle);
return true;
}
return false;
}
/**
* Utility method to determine weather it is needed to create a XSLT template
*
* @param synCtx current message
* @return true if it is needed to create a new XSLT template
*/
private boolean isCreationOrRecreationRequired(MessageContext synCtx) {
// Derive actual key from message context
String generatedXsltKey = xsltKey.evaluateValue(synCtx);
// if there are no cachedTemplates inside cachedTemplatesMap or
// if the template related to this generated key is not cached
// then it need to be cached
if (cachedTemplatesMap.isEmpty() || !cachedTemplatesMap.containsKey(generatedXsltKey)) {
// this is a creation case
return true;
} else {
// build transformer - if necessary
Entry dp = synCtx.getConfiguration().getEntryDefinition(generatedXsltKey);
// if the xsltKey refers to a dynamic resource, and if it has been expired
// it is a recreation case
return dp != null && dp.isDynamic() && (!dp.isCached() || dp.isExpired());
}
}
public Value getXsltKey() {
return xsltKey;
}
public void setXsltKey(Value xsltKey) {
this.xsltKey = xsltKey;
}
public void setSourceXPathString(String sourceXPathString) {
this.source.setXPathString(sourceXPathString);
}
public SynapseXPath getSource() {
return source.getXPath();
}
public void setSource(SynapseXPath source) {
this.source.setXPath(source);
}
public String getTargetPropertyName() {
return targetPropertyName;
}
public void setTargetPropertyName(String targetPropertyName) {
this.targetPropertyName = targetPropertyName;
}
public void addProperty(MediatorProperty p) {
properties.add(p);
}
/**
* Set the properties defined in the mediator as parameters on the stylesheet.
*
* @param transformer Transformer instance
* @param synCtx MessageContext instance
* @param synLog SynapseLog instance
*/
private void applyProperties(Transformer transformer, MessageContext synCtx,
SynapseLog synLog) {
for (MediatorProperty prop : properties) {
if (prop != null) {
String value;
if (prop.getValue() != null) {
value = prop.getValue();
} else {
value = prop.getExpression().stringValueOf(synCtx);
}
if (synLog.isTraceOrDebugEnabled()) {
if (value == null) {
synLog.traceOrDebug("Not setting parameter '" + prop.getName() + "'");
} else {
synLog.traceOrDebug("Setting parameter '" + prop.getName() + "' to '"
+ value + "'");
}
}
if (value != null) {
transformer.setParameter(prop.getName(), value);
}
}
}
}
/**
* Add a feature to be set on the {@link TransformerFactory} used by this mediator instance.
* This method can also be used to enable some Synapse specific optimizations and
* enhancements as described in the documentation of this class.
*
* @param featureName The name of the feature
* @param isFeatureEnable the desired state of the feature
*
* @see TransformerFactory#setFeature(String, boolean)
* @see FastXSLTMediator
*/
public void addFeature(String featureName, boolean isFeatureEnable) {
MediatorProperty mp = new MediatorProperty();
mp.setName(featureName);
if (isFeatureEnable) {
mp.setValue("true");
} else {
mp.setValue("false");
}
transformerFactoryFeatures.add(mp);
if (USE_DOM_SOURCE_AND_RESULTS.equals(featureName)) {
if (isFeatureEnable) {
sourceBuilderFactory = new DOOMSourceBuilderFactory();
resultBuilderFactory = new DOOMResultBuilderFactory();
}
} else {
try {
transFact.setFeature(featureName, isFeatureEnable);
} catch (TransformerConfigurationException e) {
String msg = "Error occurred when setting features to the TransformerFactory";
log.error(msg, e);
throw new SynapseException(msg, e);
}
}
}
/**
* Add an attribute to be set on the {@link TransformerFactory} used by this mediator instance.
* This method can also be used to enable some Synapse specific optimizations and
* enhancements as described in the documentation of this class.
*
* @param name The name of the feature
* @param value should this feature enable?
*
* @see TransformerFactory#setAttribute(String, Object)
* @see FastXSLTMediator
*/
public void addAttribute(String name, String value) {
MediatorProperty mp = new MediatorProperty();
mp.setName(name);
mp.setValue(value);
transformerFactoryAttributes.add(mp);
if (SOURCE_BUILDER_FACTORY.equals(name) || RESULT_BUILDER_FACTORY.equals(name)) {
Object instance;
try {
instance = Class.forName(value).newInstance();
} catch (ClassNotFoundException e) {
String msg = "The class specified by the " + name + " attribute was not found";
log.error(msg, e);
throw new SynapseException(msg, e);
} catch (Exception e) {
String msg = "The class " + value + " could not be instantiated";
log.error(msg, e);
throw new SynapseException(msg, e);
}
if (SOURCE_BUILDER_FACTORY.equals(name)) {
sourceBuilderFactory = (SourceBuilderFactory)instance;
} else {
resultBuilderFactory = (ResultBuilderFactory)instance;
}
} else {
try {
transFact.setAttribute(name, value);
} catch (IllegalArgumentException e) {
String msg = "Error occurred when setting attribute to the TransformerFactory";
log.error(msg, e);
throw new SynapseException(msg, e);
}
}
}
/**
* @return Return the features explicitly set to the TransformerFactory through this mediator.
*/
public List<MediatorProperty> getFeatures(){
return transformerFactoryFeatures;
}
/**
* @return Return the attributes explicitly set to the TransformerFactory through this mediator.
*/
public List<MediatorProperty> getAttributes(){
return transformerFactoryAttributes;
}
public void addAllProperties(List<MediatorProperty> list) {
properties.addAll(list);
}
public List<MediatorProperty> getProperties() {
return properties;
}
public ResourceMap getResourceMap() {
return resourceMap;
}
public void setResourceMap(ResourceMap resourceMap) {
this.resourceMap = resourceMap;
}
public boolean isContentAware() {
return false;
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void init(SynapseEnvironment arg0) {
PassThroughConfiguration conf = PassThroughConfiguration.getInstance();
bufferSizeSupport =conf.getIOBufferSize();
}
}