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