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