Coverage Report - org.mule.module.xml.transformer.XsltTransformer
 
Classes in this File Line Coverage Branch Coverage Complexity
XsltTransformer
0%
0/80
0%
0/22
0
XsltTransformer$1
0%
0/6
N/A
0
XsltTransformer$DefaultErrorListener
0%
0/12
0%
0/2
0
XsltTransformer$PooledXsltTransformerFactory
0%
0/15
0%
0/6
0
 
 1  
 /*
 2  
  * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 3  
  * The software in this package is published under the terms of the CPAL v1.0
 4  
  * license, a copy of which has been included with this distribution in the
 5  
  * LICENSE.txt file.
 6  
  */
 7  
 package org.mule.module.xml.transformer;
 8  
 
 9  
 import org.mule.api.MuleMessage;
 10  
 import org.mule.api.lifecycle.InitialisationException;
 11  
 import org.mule.api.transformer.Transformer;
 12  
 import org.mule.api.transformer.TransformerException;
 13  
 import org.mule.config.i18n.CoreMessages;
 14  
 import org.mule.module.xml.util.LocalURIResolver;
 15  
 import org.mule.module.xml.util.XMLUtils;
 16  
 import org.mule.util.ClassUtils;
 17  
 import org.mule.util.IOUtils;
 18  
 import org.mule.util.StringUtils;
 19  
 
 20  
 import java.io.StringReader;
 21  
 import java.util.HashMap;
 22  
 import java.util.Iterator;
 23  
 import java.util.Map;
 24  
 import java.util.Map.Entry;
 25  
 
 26  
 import javax.xml.transform.ErrorListener;
 27  
 import javax.xml.transform.OutputKeys;
 28  
 import javax.xml.transform.Result;
 29  
 import javax.xml.transform.Source;
 30  
 import javax.xml.transform.TransformerFactory;
 31  
 import javax.xml.transform.TransformerFactoryConfigurationError;
 32  
 import javax.xml.transform.URIResolver;
 33  
 import javax.xml.transform.stream.StreamSource;
 34  
 
 35  
 import org.apache.commons.pool.BasePoolableObjectFactory;
 36  
 import org.apache.commons.pool.impl.GenericObjectPool;
 37  
 
 38  
 /**
 39  
  * <code>XsltTransformer</code> performs an XSLT transform on a DOM (or other XML-ish)
 40  
  * object.
 41  
  * <p/>
 42  
  * This transformer maintains a pool of {@link javax.xml.transform.Transformer} objects to speed up processing of concurrent requests.
 43  
  * The pool can be configured using {@link #setMaxIdleTransformers(int)}.
 44  
  * <p/>
 45  
  * Parameter can also be set as part of the transformation context and these can be mapped to conent in the current message using
 46  
  * property extractors or can be fixed values.
 47  
  * <p/>
 48  
  * <p/>
 49  
  * For example, the current event's message has a property named "myproperty", also you want to generate a uuid as a
 50  
  * parameter. To do this you can define context properties that can provide an expression to be evaluated on the current
 51  
  * message.
 52  
  * </p>
 53  
  * <p>
 54  
  * Example Configuration:
 55  
  * </p>
 56  
  * <p/>
 57  
  * <pre>
 58  
  *  &lt;mxml:xslt-transformer name=&quot;MyXsltTransformer&quot; xslFile=&quot;myXslFile.xsl&quot;&amp;gt
 59  
  *      &lt;context-property name=&quot;myParameter&quot; value=&quot;#[head:myproperty]&quot;/&amp;gt
 60  
  *      &lt;context-property name=&quot;myParameter2&quot; value=&quot;#[function:uuid]&quot;/&amp;gt
 61  
  *  &lt;/mxml:xslt-transformer&amp;gt
 62  
  * </pre>
 63  
  * <p/>
 64  
  * <p>
 65  
  * The 'header' expression pulls a header from the current message and 'function' can execute a set of arbitrary functions.
 66  
  * You can also pass in static values by ommitting the expression prefix '#['.
 67  
  * </p>
 68  
  * <p/>
 69  
  * In addition to being able to pass in an XSLT file you can also define templates inline. For example -
 70  
  * <p/>
 71  
  * <pre>
 72  
  *  &lt;mxml:xslt-transformer name=&quot;MyXsltTransformer&quot;&amp;gt
 73  
  *      &lt;mxml:xslt-text&amp;gt
 74  
  *          <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://test.com" version="2.0">
 75  
  * <p/>
 76  
  *                <xsl:param name="echo"/>
 77  
  * <p/>
 78  
  *               <xsl:template match="/">
 79  
  *                   <echo-value>
 80  
  *                       <xsl:value-of select="$echo"/>
 81  
  *                   </echo-value>
 82  
  *               </xsl:template>
 83  
  *           </xsl:stylesheet>
 84  
  *  &lt;/mxml:xslt-text&amp;gt
 85  
  * </pre>
 86  
  */
 87  
 
 88  0
 public class XsltTransformer extends AbstractXmlTransformer
 89  
 {
 90  
     // keep at least 1 XSLT Transformer ready by default
 91  
     private static final int MIN_IDLE_TRANSFORMERS = 1;
 92  
     // keep max. 32 XSLT Transformers around by default
 93  
     private static final int MAX_IDLE_TRANSFORMERS = 32;
 94  
     // MAX_IDLE is also the total limit
 95  
     private static final int MAX_ACTIVE_TRANSFORMERS = MAX_IDLE_TRANSFORMERS;
 96  
 
 97  
     //Saxon shipped with Mule
 98  
     public static final String PREFERRED_TRANSFORMER_FACTORY = "net.sf.saxon.TransformerFactoryImpl";
 99  
 
 100  
     protected final GenericObjectPool transformerPool;
 101  
 
 102  
     /**
 103  
      * Default to Saxon
 104  
      */
 105  0
     private volatile String xslTransformerFactoryClassName = PREFERRED_TRANSFORMER_FACTORY;
 106  
     private volatile String xslFile;
 107  
     private volatile String xslt;
 108  
     private volatile Map contextProperties;
 109  
 
 110  
     private URIResolver uriResolver;
 111  
 
 112  
     public XsltTransformer()
 113  
     {
 114  0
         super();
 115  0
         transformerPool = new GenericObjectPool(new PooledXsltTransformerFactory());
 116  0
         transformerPool.setMinIdle(MIN_IDLE_TRANSFORMERS);
 117  0
         transformerPool.setMaxIdle(MAX_IDLE_TRANSFORMERS);
 118  0
         transformerPool.setMaxActive(MAX_ACTIVE_TRANSFORMERS);
 119  0
         uriResolver = new LocalURIResolver();
 120  0
         contextProperties = new HashMap();
 121  0
     }
 122  
 
 123  
     public XsltTransformer(String xslFile)
 124  
     {
 125  0
         this();
 126  0
         this.setXslFile(xslFile);
 127  0
     }
 128  
 
 129  
     @Override
 130  
     public void initialise() throws InitialisationException
 131  
     {
 132  0
         logger.debug("Initialising transformer: " + this);
 133  
         try
 134  
         {
 135  
             // Only load the file once at initialize time
 136  0
             if (xslFile != null)
 137  
             {
 138  0
                 this.xslt = IOUtils.getResourceAsString(xslFile, getClass());
 139  
             }
 140  0
             transformerPool.addObject();
 141  
         }
 142  0
         catch (Throwable te)
 143  
         {
 144  0
             throw new InitialisationException(te, this);
 145  0
         }
 146  0
     }
 147  
 
 148  
     /**
 149  
      * Transform, using XSLT, a XML String to another String.
 150  
      *
 151  
      * @return The result in the type specified by the user
 152  
      */
 153  
     @Override
 154  
     public Object transformMessage(MuleMessage message, String encoding) throws TransformerException
 155  
     {
 156  0
         Object src = message.getPayload();
 157  
         try
 158  
         {
 159  0
             Source sourceDoc = XMLUtils.toXmlSource(getXMLInputFactory(), isUseStaxSource(), src);
 160  0
             if (sourceDoc == null)
 161  
             {
 162  0
                 return null;
 163  
             }
 164  
 
 165  0
             ResultHolder holder = getResultHolder(returnType.getType());
 166  
 
 167  
             // If the users hasn't specified a class, lets return the same type they gave us
 168  0
             if (holder == null)
 169  
             {
 170  0
                 holder = getResultHolder(src.getClass());
 171  
             }
 172  
 
 173  
             // If we still don't have a result type, lets fall back to using a DelayedResult
 174  
             // as it is the most efficient.
 175  0
             if (holder == null || DelayedResult.class.equals(returnType.getType()))
 176  
             {
 177  0
                 return getDelayedResult(message, encoding, sourceDoc);
 178  
             }
 179  
 
 180  0
             doTransform(message, encoding, sourceDoc, holder.getResult());
 181  
 
 182  0
             return holder.getResultObject();
 183  
         }
 184  0
         catch (Exception e)
 185  
         {
 186  0
             throw new TransformerException(this, e);
 187  
         }
 188  
     }
 189  
 
 190  
     protected Object getDelayedResult(final MuleMessage message, final String encoding, final Source sourceDoc)
 191  
     {
 192  0
         return new DelayedResult()
 193  0
         {
 194  
             private String systemId;
 195  
 
 196  
             public void write(Result result) throws Exception
 197  
             {
 198  0
                 doTransform(message, encoding, sourceDoc, result);
 199  0
             }
 200  
 
 201  
             public String getSystemId()
 202  
             {
 203  0
                 return systemId;
 204  
             }
 205  
 
 206  
             public void setSystemId(String systemId)
 207  
             {
 208  0
                 this.systemId = systemId;
 209  0
             }
 210  
         };
 211  
     }
 212  
 
 213  
     protected void doTransform(MuleMessage message, String encoding, Source sourceDoc, Result result)
 214  
             throws Exception
 215  
     {
 216  0
         DefaultErrorListener errorListener = new DefaultErrorListener(this);
 217  0
         javax.xml.transform.Transformer transformer = null;
 218  
 
 219  
         try
 220  
         {
 221  0
             transformer = (javax.xml.transform.Transformer) transformerPool.borrowObject();
 222  
 
 223  0
             transformer.setErrorListener(errorListener);
 224  0
             transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
 225  
 
 226  
             // set transformation parameters
 227  0
             if (contextProperties != null)
 228  
             {
 229  0
                 for (Iterator i = contextProperties.entrySet().iterator(); i.hasNext();)
 230  
                 {
 231  0
                     Map.Entry parameter = (Entry) i.next();
 232  0
                     String key = (String) parameter.getKey();
 233  0
                     transformer.setParameter(key, evaluateTransformParameter(key, parameter.getValue(), message));
 234  0
                 }
 235  
             }
 236  
 
 237  0
             transformer.transform(sourceDoc, result);
 238  
 
 239  0
             if (errorListener.isError())
 240  
             {
 241  0
                 throw errorListener.getException();
 242  
             }
 243  
         }
 244  
         finally
 245  
         {
 246  0
             if (transformer != null)
 247  
             {
 248  
                 // clear transformation parameters before returning transformer to the
 249  
                 // pool
 250  0
                 transformer.clearParameters();
 251  
 
 252  0
                 transformerPool.returnObject(transformer);
 253  
             }
 254  
         }
 255  0
     }
 256  
 
 257  
     /**
 258  
      * Returns the name of the currently configured javax.xml.transform.Transformer
 259  
      * factory class used to create XSLT Transformers.
 260  
      *
 261  
      * @return a TransformerFactory class name or <code>null</code> if none has been
 262  
      *         configured
 263  
      */
 264  
     public String getXslTransformerFactory()
 265  
     {
 266  0
         return xslTransformerFactoryClassName;
 267  
     }
 268  
 
 269  
     /**
 270  
      * Configures the javax.xml.transform.Transformer factory class
 271  
      *
 272  
      * @param xslTransformerFactory the name of the TransformerFactory class to use
 273  
      */
 274  
     public void setXslTransformerFactory(String xslTransformerFactory)
 275  
     {
 276  0
         this.xslTransformerFactoryClassName = xslTransformerFactory;
 277  0
     }
 278  
 
 279  
     /**
 280  
      * @return Returns the xslFile.
 281  
      */
 282  
     public String getXslFile()
 283  
     {
 284  0
         return xslFile;
 285  
     }
 286  
 
 287  
     /**
 288  
      * @param xslFile The xslFile to set.
 289  
      */
 290  
     public void setXslFile(String xslFile)
 291  
     {
 292  0
         this.xslFile = xslFile;
 293  0
     }
 294  
 
 295  
     public String getXslt()
 296  
     {
 297  0
         return xslt;
 298  
     }
 299  
 
 300  
     public void setXslt(String xslt)
 301  
     {
 302  0
         this.xslt = xslt;
 303  0
     }
 304  
 
 305  
     public URIResolver getUriResolver()
 306  
     {
 307  0
         return uriResolver;
 308  
     }
 309  
 
 310  
     public void setUriResolver(URIResolver uriResolver)
 311  
     {
 312  0
         this.uriResolver = uriResolver;
 313  0
     }
 314  
 
 315  
     /**
 316  
      * Returns the StreamSource corresponding to xslt (which should have been loaded
 317  
      * in {@link #initialise()}).
 318  
      * 
 319  
      * @return The StreamSource
 320  
      */
 321  
     protected StreamSource getStreamSource() throws InitialisationException
 322  
     {
 323  0
         if (xslt == null)
 324  
         {
 325  0
             throw new InitialisationException(CoreMessages.propertiesNotSet("xsl-file or xsl-text"), this);
 326  
         }
 327  
         else
 328  
         {
 329  0
             return new StreamSource(new StringReader(xslt));
 330  
         }
 331  
     }
 332  
 
 333  0
     protected class PooledXsltTransformerFactory extends BasePoolableObjectFactory
 334  
     {
 335  
         @Override
 336  
         public Object makeObject() throws Exception
 337  
         {
 338  0
             StreamSource source = XsltTransformer.this.getStreamSource();
 339  0
             String factoryClassName = XsltTransformer.this.getXslTransformerFactory();
 340  
             TransformerFactory factory;
 341  
 
 342  0
             if (PREFERRED_TRANSFORMER_FACTORY.equals(factoryClassName) && !ClassUtils.isClassOnPath(factoryClassName, getClass()))
 343  
             {
 344  0
                 logger.warn("Preferred Transfomer Factory " + PREFERRED_TRANSFORMER_FACTORY + " not on classpath and no default is set, defaulting to JDK");
 345  0
                 factoryClassName = null;
 346  
             }
 347  
 
 348  0
             if (StringUtils.isNotEmpty(factoryClassName))
 349  
             {
 350  
 
 351  0
                 factory = (TransformerFactory) ClassUtils.instanciateClass(factoryClassName,
 352  
                         ClassUtils.NO_ARGS, this.getClass());
 353  
             }
 354  
             else
 355  
             {
 356  
                 // fall back to JDK default
 357  
                 try
 358  
                 {
 359  0
                     factory = TransformerFactory.newInstance();
 360  
                 }
 361  0
                 catch (TransformerFactoryConfigurationError e)
 362  
                 {
 363  0
                     System.setProperty("javax.xml.transform.TransformerFactory", XMLUtils.TRANSFORMER_FACTORY_JDK5);
 364  0
                     factory = TransformerFactory.newInstance();
 365  0
                 }
 366  
             }
 367  
 
 368  0
             factory.setURIResolver(getUriResolver());
 369  0
             return factory.newTransformer(source);
 370  
         }
 371  
     }
 372  
 
 373  
     protected class DefaultErrorListener implements ErrorListener
 374  
     {
 375  0
         private TransformerException e = null;
 376  
         private final Transformer trans;
 377  
 
 378  
         public DefaultErrorListener(Transformer trans)
 379  0
         {
 380  0
             this.trans = trans;
 381  0
         }
 382  
 
 383  
         public TransformerException getException()
 384  
         {
 385  0
             return e;
 386  
         }
 387  
 
 388  
         public boolean isError()
 389  
         {
 390  0
             return e != null;
 391  
         }
 392  
 
 393  
         public void error(javax.xml.transform.TransformerException exception)
 394  
                 throws javax.xml.transform.TransformerException
 395  
         {
 396  0
             e = new TransformerException(trans, exception);
 397  0
         }
 398  
 
 399  
         public void fatalError(javax.xml.transform.TransformerException exception)
 400  
                 throws javax.xml.transform.TransformerException
 401  
         {
 402  0
             e = new TransformerException(trans, exception);
 403  0
         }
 404  
 
 405  
         public void warning(javax.xml.transform.TransformerException exception)
 406  
                 throws javax.xml.transform.TransformerException
 407  
         {
 408  0
             logger.warn(exception.getMessage());
 409  0
         }
 410  
     }
 411  
 
 412  
     /**
 413  
      * @return The current maximum number of allowable active transformer objects in
 414  
      *         the pool
 415  
      */
 416  
     public int getMaxActiveTransformers()
 417  
     {
 418  0
         return transformerPool.getMaxActive();
 419  
     }
 420  
 
 421  
     /**
 422  
      * Sets the the current maximum number of active transformer objects allowed in the
 423  
      * pool
 424  
      *
 425  
      * @param maxActiveTransformers New maximum size to set
 426  
      */
 427  
     public void setMaxActiveTransformers(int maxActiveTransformers)
 428  
     {
 429  0
         transformerPool.setMaxActive(maxActiveTransformers);
 430  0
     }
 431  
 
 432  
     /**
 433  
      * @return The current maximum number of allowable idle transformer objects in the
 434  
      *         pool
 435  
      */
 436  
     public int getMaxIdleTransformers()
 437  
     {
 438  0
         return transformerPool.getMaxIdle();
 439  
     }
 440  
 
 441  
     /**
 442  
      * Sets the the current maximum number of idle transformer objects allowed in the pool
 443  
      *
 444  
      * @param maxIdleTransformers New maximum size to set
 445  
      */
 446  
     public void setMaxIdleTransformers(int maxIdleTransformers)
 447  
     {
 448  0
         transformerPool.setMaxIdle(maxIdleTransformers);
 449  0
     }
 450  
 
 451  
     /**
 452  
      * Gets the parameters to be used when applying the transformation
 453  
      *
 454  
      * @return a map of the parameter names and associated values
 455  
      * @see javax.xml.transform.Transformer#setParameter(java.lang.String,
 456  
      *      java.lang.Object)
 457  
      */
 458  
     public Map getContextProperties()
 459  
     {
 460  0
         return contextProperties;
 461  
     }
 462  
 
 463  
     /**
 464  
      * Sets the parameters to be used when applying the transformation
 465  
      *
 466  
      * @param contextProperties a map of the parameter names and associated values
 467  
      * @see javax.xml.transform.Transformer#setParameter(java.lang.String,
 468  
      *      java.lang.Object)
 469  
      */
 470  
     public void setContextProperties(Map contextProperties)
 471  
     {
 472  0
         this.contextProperties = contextProperties;
 473  0
     }
 474  
 
 475  
     /**
 476  
      * Returns the value to be set for the parameter. This method is called for each
 477  
      * parameter before it is set on the transformer. The purpose of this method is to
 478  
      * allow dynamic parameters related to the event (usually message properties) to be
 479  
      * used. Any attribute of the current MuleEvent can be accessed using Property Extractors
 480  
      * such as JXpath, bean path or header retrieval.
 481  
      *
 482  
      * @param name  the name of the parameter. The name isn't used for this implementation but is exposed as a
 483  
      *              param for classes that may need it.
 484  
      * @param value the value of the paramter
 485  
      * @return the object to be set as the parameter value
 486  
      * @throws TransformerException
 487  
      */
 488  
     protected Object evaluateTransformParameter(String name, Object value, MuleMessage message) throws TransformerException
 489  
     {
 490  0
         if (value instanceof String)
 491  
         {
 492  0
             return muleContext.getExpressionManager().parse(value.toString(), message);
 493  
         }
 494  
 
 495  0
         return value;
 496  
     }
 497  
 }