View Javadoc

1   /*
2    * $Id: XsltTransformer.java 11728 2008-05-13 07:31:11Z dirk.olmes $
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.transformers.xml;
12  
13  import org.mule.config.i18n.CoreMessages;
14  import org.mule.impl.RequestContext;
15  import org.mule.umo.UMOEventContext;
16  import org.mule.umo.lifecycle.InitialisationException;
17  import org.mule.umo.transformer.TransformerException;
18  import org.mule.umo.transformer.UMOTransformer;
19  import org.mule.util.ClassUtils;
20  import org.mule.util.IOUtils;
21  import org.mule.util.StringUtils;
22  
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.StringReader;
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.Source;
33  import javax.xml.transform.Transformer;
34  import javax.xml.transform.TransformerFactory;
35  import javax.xml.transform.URIResolver;
36  import javax.xml.transform.stream.StreamSource;
37  
38  import org.apache.commons.jxpath.JXPathContext;
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   */
46  
47  public class XsltTransformer extends AbstractXmlTransformer
48  {
49      // keep at least 1 XSLT Transformer ready by default
50      private static final int MIN_IDLE_TRANSFORMERS = 1;
51      // keep max. 32 XSLT Transformers around by default
52      private static final int MAX_IDLE_TRANSFORMERS = 32;
53      // MAX_IDLE is also the total limit
54      private static final int MAX_ACTIVE_TRANSFORMERS = MAX_IDLE_TRANSFORMERS;
55      // Prefix to use in a parameter to specify it is an expression that must be evaluated
56      private static final String PARAM_EVAL_TOKEN = "#";
57  
58      protected final GenericObjectPool transformerPool;
59  
60      private volatile String xslTransformerFactoryClassName;
61      private volatile String xslFile;
62      private volatile String xslt;
63      private volatile Map transformParameters;
64      private URIResolver uriResolver;
65  
66      public XsltTransformer()
67      {
68          super();
69          transformerPool = new GenericObjectPool(new PooledXsltTransformerFactory());
70          transformerPool.setMinIdle(MIN_IDLE_TRANSFORMERS);
71          transformerPool.setMaxIdle(MAX_IDLE_TRANSFORMERS);
72          transformerPool.setMaxActive(MAX_ACTIVE_TRANSFORMERS);
73          uriResolver = new LocalURIResolver();
74      }
75  
76      /**
77       * @see org.mule.umo.lifecycle.Initialisable#initialise()
78       */
79      // @Override
80      public void initialise() throws InitialisationException
81      {
82          try
83          {
84              transformerPool.addObject();
85          }
86          catch (Throwable te)
87          {
88              throw new InitialisationException(te, this);
89          }
90      }
91  
92      /**
93       * Transform, using XSLT, a XML String to another String.
94       *
95       * @param src The source XML (String, byte[], DOM, etc.)
96       * @return The result String (or DOM)
97       */
98      public Object doTransform(Object src, String encoding) throws TransformerException
99      {
100         try
101         {
102             Source sourceDoc = this.getXmlSource(src);
103             if (sourceDoc == null)
104             {
105                 return null;
106             }
107 
108             ResultHolder holder = getResultHolder(returnClass);
109             if (holder == null)
110             {
111                 holder = getResultHolder(src.getClass());
112             }
113 
114             DefaultErrorListener errorListener = new DefaultErrorListener(this);
115             Transformer transformer = null;
116             Object result;
117 
118             try
119             {
120                 transformer = (Transformer) transformerPool.borrowObject();
121 
122                 transformer.setErrorListener(errorListener);
123                 transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
124 
125                 // set transformation parameters
126                 if (transformParameters != null)
127                 {
128                     for (Iterator i = transformParameters.entrySet().iterator(); i.hasNext();)
129                     {
130                         Map.Entry parameter = (Entry) i.next();
131                         String key = (String) parameter.getKey();
132                         transformer.setParameter(key, evaluateTransformParameter(key, parameter.getValue()));
133                     }
134                 }
135 
136                 transformer.transform(sourceDoc, holder.getResult());
137                 result = holder.getResultObject();
138 
139                 if (errorListener.isError())
140                 {
141                     throw errorListener.getException();
142                 }
143             }
144             finally
145             {
146                 if (transformer != null)
147                 {
148                     // clear transformation parameters before returning transformer to the
149                     // pool
150                     transformer.clearParameters();
151 
152                     transformerPool.returnObject(transformer);
153                 }
154             }
155 
156             return result;
157         }
158         catch (Exception e)
159         {
160             throw new TransformerException(this, e);
161         }
162     }
163 
164     /**
165      * Returns the name of the currently configured javax.xml.transform.Transformer
166      * factory class used to create XSLT Transformers.
167      *
168      * @return a TransformerFactory class name or <code>null</code> if none has been
169      *         configured
170      */
171     public String getXslTransformerFactory()
172     {
173         return xslTransformerFactoryClassName;
174     }
175 
176     /**
177      * Configures the javax.xml.transform.Transformer factory class
178      *
179      * @param xslTransformerFactory the name of the TransformerFactory class to use
180      */
181     public void setXslTransformerFactory(String xslTransformerFactory)
182     {
183         this.xslTransformerFactoryClassName = xslTransformerFactory;
184     }
185 
186     /**
187      * @return Returns the xslFile.
188      */
189     public String getXslFile()
190     {
191         return xslFile;
192     }
193 
194     /**
195      * @param xslFile The xslFile to set.
196      */
197     public void setXslFile(String xslFile)
198     {
199         this.xslFile = xslFile;
200     }
201 
202     public String getXslt()
203     {
204         return xslt;
205     }
206 
207     public void setXslt(String xslt)
208     {
209         this.xslt = xslt;
210     }
211 
212     /**
213      * Returns the StreamSource corresponding to xslFile
214      *
215      * @return The StreamSource
216      */
217     protected StreamSource getStreamSource() throws InitialisationException
218     {
219         if (xslt != null)
220         {
221             return new StreamSource(new StringReader(xslt));
222         }
223 
224         if (xslFile == null)
225         {
226             throw new InitialisationException(CoreMessages.objectIsNull("xslFile"), this);
227         }
228 
229         InputStream is;
230         try
231         {
232             is = IOUtils.getResourceAsStream(xslFile, getClass());
233         }
234         catch (IOException e)
235         {
236             throw new InitialisationException(e, this);
237         }
238         if (is != null)
239         {
240             return new StreamSource(is);
241         }
242         else
243         {
244             throw new InitialisationException(CoreMessages.failedToLoad(xslFile), this);
245         }
246     }
247 
248     // @Override
249     public Object clone() throws CloneNotSupportedException
250     {
251         XsltTransformer clone = (XsltTransformer) super.clone();
252 
253         try
254         {
255             if (clone.nextTransformer == null)
256             {
257                 clone.initialise();
258             }
259         }
260         catch (Exception e)
261         {
262             throw new CloneNotSupportedException(e.getMessage());
263         }
264 
265         return clone;
266     }
267 
268     protected class PooledXsltTransformerFactory extends BasePoolableObjectFactory
269     {
270         public Object makeObject() throws Exception
271         {
272             StreamSource source = XsltTransformer.this.getStreamSource();
273             String factoryClassName = XsltTransformer.this.getXslTransformerFactory();
274             TransformerFactory factory;
275 
276             if (StringUtils.isNotEmpty(factoryClassName))
277             {
278                 factory = (TransformerFactory) ClassUtils.instanciateClass(factoryClassName,
279                         ClassUtils.NO_ARGS, this.getClass());
280             }
281             else
282             {
283                 // fall back to JDK default
284                 factory = TransformerFactory.newInstance();
285             }
286 
287             factory.setURIResolver(uriResolver);
288             return factory.newTransformer(source);
289         }
290     }
291 
292     protected class DefaultErrorListener implements ErrorListener
293     {
294         private TransformerException e = null;
295         private final UMOTransformer trans;
296 
297         public DefaultErrorListener(UMOTransformer trans)
298         {
299             this.trans = trans;
300         }
301 
302         public TransformerException getException()
303         {
304             return e;
305         }
306 
307         public boolean isError()
308         {
309             return e != null;
310         }
311 
312         public void error(javax.xml.transform.TransformerException exception)
313                 throws javax.xml.transform.TransformerException
314         {
315             e = new TransformerException(trans, exception);
316         }
317 
318         public void fatalError(javax.xml.transform.TransformerException exception)
319                 throws javax.xml.transform.TransformerException
320         {
321             e = new TransformerException(trans, exception);
322         }
323 
324         public void warning(javax.xml.transform.TransformerException exception)
325                 throws javax.xml.transform.TransformerException
326         {
327             logger.warn(exception.getMessage());
328         }
329     }
330 
331     /**
332      * @return The current maximum number of allowable idle transformer objects in the
333      *         pool
334      */
335     public int getMaxActiveTransformers()
336     {
337         return transformerPool.getMaxActive();
338     }
339 
340     /**
341      * Sets the the current maximum number of idle transformer objects allowed in the pool
342      *
343      * @param maxActiveTransformers New maximum size to set
344      */
345     public void setMaxActiveTransformers(int maxActiveTransformers)
346     {
347         transformerPool.setMaxActive(maxActiveTransformers);
348     }
349 
350     /**
351      * @return The current maximum number of allowable idle transformer objects in the
352      *         pool
353      */
354     public int getMaxIdleTransformers()
355     {
356         return transformerPool.getMaxIdle();
357     }
358 
359     /**
360      * Sets the the current maximum number of idle transformer objects allowed in the pool
361      *
362      * @param maxIdleTransformers New maximum size to set
363      */
364     public void setMaxIdleTransformers(int maxIdleTransformers)
365     {
366         transformerPool.setMaxIdle(maxIdleTransformers);
367     }
368 
369     /**
370      * Gets the parameters to be used when applying the transformation
371      *
372      * @return a map of the parameter names and associated values
373      * @see javax.xml.transform.Transformer#setParameter(java.lang.String,
374      *      java.lang.Object)
375      */
376     public Map getTransformParameters()
377     {
378         return transformParameters;
379     }
380 
381     /**
382      * Sets the parameters to be used when applying the transformation
383      *
384      * @param transformParameters a map of the parameter names and associated values
385      * @see javax.xml.transform.Transformer#setParameter(java.lang.String,
386      *      java.lang.Object)
387      */
388     public void setTransformParameters(Map transformParameters)
389     {
390         this.transformParameters = transformParameters;
391     }
392 
393     /**
394      * <p>
395      * Returns the value to be set for the parameter. This method is called for each
396      * parameter before it is set on the transformer. The purpose of this method is to
397      * allow dynamic parameters related to the event (usually message properties) to be
398      * used. Any attribute of the current UMOEventContext can be accessed using JXPath.
399      * </p>
400      * <p>
401      * For example: If the current event's message has a property named "myproperty", to
402      * pass this in you would set the transform parameter's value to be
403      * "#getProperty(message,'myproperty')".
404      * </p>
405      * <p>
406      * Example Configuration:
407      * </p>
408      * <p/>
409      * <pre>
410      *  &lt;transformer name=&quot;MyXsltTransformer&quot; className=&quot;org.mule.transformers.xml.XsltTransformer&quot;&amp;gt
411      *      &lt;properties&gt;
412      *          &lt;property name=&quot;xslFile&quot; value=&quot;myXslFile.xsl&quot;/&amp;gt
413      *          &lt;map name=&quot;transformParameters&quot;&amp;gt
414      *              &lt;property name=&quot;myParameter&quot; value=&quot;#getProperty(message,'myproperty')&quot;/&amp;gt
415      *          &lt;/map&amp;gt
416      *      &lt;/properties&amp;gt
417      *  &lt;/transformer&amp;gt
418      * </pre>
419      * <p/>
420      * <p>
421      * Only parameter values that begin with # are evalued in this maner. Values that do
422      * not start with # are returned as is. Values that start with ## are returned as is
423      * starting from the second character. For example "##myparameter" would be passed
424      * into the transformer as "#myparameter"
425      * </p>
426      * <p>
427      * This method may be overloaded by a sub class to provide a different dynamic
428      * parameter implementation.
429      * </p>
430      *
431      * @param name  the name of the parameter
432      * @param value the value of the paramter
433      * @return the object to be set as the parameter value
434      * @throws TransformerException
435      */
436     protected Object evaluateTransformParameter(String name, Object value) throws TransformerException
437     {
438         if (value instanceof String)
439         {
440             String stringValue = (String) value;
441 
442             if (!stringValue.startsWith(PARAM_EVAL_TOKEN))
443             {
444                 return stringValue;
445 
446             }
447             else if (stringValue.startsWith(PARAM_EVAL_TOKEN + PARAM_EVAL_TOKEN))
448             {
449                 return stringValue.substring(1);
450 
451             }
452             else
453             {
454 
455                 UMOEventContext context = RequestContext.getEventContext();
456 
457                 if (context == null)
458                 {
459                     throw new TransformerException(CoreMessages.noCurrentEventForTransformer(), this);
460                 }
461 
462                 JXPathContext jxpathContext = JXPathContext.newContext(context);
463                 return jxpathContext.getValue(stringValue.substring(1));
464             }
465         }
466 
467         return value;
468     }
469 
470     private class LocalURIResolver implements URIResolver
471     {
472         public Source resolve(String href, String base)
473                 throws javax.xml.transform.TransformerException
474         {
475             try
476             {
477                 return new StreamSource(IOUtils.getResourceAsStream(href, getClass()));
478             }
479             catch (IOException e)
480             {
481                 throw new javax.xml.transform.TransformerException(e);
482             }
483         }
484     }
485 }