View Javadoc

1   /*
2    * $Id: AbstractTransformer.java 10089 2007-12-14 10:48:14Z holger $
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;
12  
13  import org.mule.MuleManager;
14  import org.mule.config.i18n.CoreMessages;
15  import org.mule.providers.NullPayload;
16  import org.mule.umo.UMOMessage;
17  import org.mule.umo.endpoint.UMOImmutableEndpoint;
18  import org.mule.umo.lifecycle.InitialisationException;
19  import org.mule.umo.transformer.TransformerException;
20  import org.mule.umo.transformer.UMOTransformer;
21  import org.mule.util.BeanUtils;
22  import org.mule.util.ClassUtils;
23  import org.mule.util.StringMessageUtils;
24  
25  import java.util.List;
26  
27  import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArrayList;
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  /**
32   * <code>AbstractTransformer</code> is a base class for all transformers.
33   * Transformations transform one object into another.
34   */
35  
36  public abstract class AbstractTransformer implements UMOTransformer
37  {
38      protected static final int DEFAULT_TRUNCATE_LENGTH = 200;
39  
40      /**
41       * logger used by this class
42       */
43      protected final Log logger = LogFactory.getLog(getClass());
44  
45      /**
46       * The return type that will be returned by the {@link #transform} method is
47       * called
48       */
49      protected Class returnClass = Object.class;
50  
51      /**
52       * The name that identifies this transformer. If none is set the class name of
53       * the transformer is used
54       */
55      protected String name = null;
56  
57      /**
58       * The endpoint that this transformer instance is configured on
59       */
60      protected UMOImmutableEndpoint endpoint = null;
61  
62      /**
63       * A list of supported Class types that the source payload passed into this
64       * transformer
65       */
66      protected final List sourceTypes = new CopyOnWriteArrayList();
67  
68      /**
69       * This is the following transformer in the chain of transformers.
70       */
71      protected UMOTransformer nextTransformer;
72  
73      /**
74       * Determines whether the transformer will throw an exception if the message
75       * passed is is not supported or the return tye is incorrect
76       */
77      private boolean ignoreBadInput = false;
78  
79      /**
80       * default constructor required for discovery
81       */
82      public AbstractTransformer()
83      {
84          name = this.generateTransformerName();
85      }
86  
87      protected Object checkReturnClass(Object object) throws TransformerException
88      {
89          if (returnClass != null)
90          {
91              if (!returnClass.isInstance(object))
92              {
93                  throw new TransformerException(
94                      CoreMessages.transformUnexpectedType(object.getClass(), returnClass),
95                      this);
96              }
97          }
98  
99          if (logger.isDebugEnabled())
100         {
101             logger.debug("The transformed object is of expected type. Type is: " +
102                     ClassUtils.getSimpleName(object.getClass()));
103         }
104 
105         return object;
106     }
107 
108     protected void registerSourceType(Class aClass)
109     {
110         if (!sourceTypes.contains(aClass))
111         {
112             sourceTypes.add(aClass);
113 
114             if (aClass.equals(Object.class))
115             {
116                 logger.debug("java.lang.Object has been added as source type for this transformer, there will be no source type checking performed");
117             }
118         }
119     }
120 
121     protected void unregisterSourceType(Class aClass)
122     {
123         sourceTypes.remove(aClass);
124     }
125 
126     /**
127      * @return transformer name
128      */
129     public String getName()
130     {
131         return name;
132     }
133 
134     /**
135      * @param string
136      */
137     public void setName(String string)
138     {
139         if (string == null)
140         {
141             string = ClassUtils.getSimpleName(this.getClass());
142         }
143 
144         logger.debug("Setting transformer name to: " + string);
145         name = string;
146     }
147 
148     /*
149      * (non-Javadoc)
150      * 
151      * @see org.mule.transformers.Transformer#getReturnClass()
152      */
153     public Class getReturnClass()
154     {
155         return returnClass;
156     }
157 
158     /*
159      * (non-Javadoc)
160      * 
161      * @see org.mule.transformers.Transformer#setReturnClass(java.lang.String)
162      */
163     public void setReturnClass(Class newClass)
164     {
165         returnClass = newClass;
166     }
167 
168     public boolean isSourceTypeSupported(Class aClass)
169     {
170         return isSourceTypeSupported(aClass, false);
171     }
172 
173     public boolean isSourceTypeSupported(Class aClass, boolean exactMatch)
174     {
175         int numTypes = sourceTypes.size();
176 
177         if (numTypes == 0)
178         {
179             return !exactMatch;
180         }
181 
182         for (int i = 0; i < numTypes; i++)
183         {
184             Class anotherClass = (Class) sourceTypes.get(i);
185             if (exactMatch)
186             {
187                 if (anotherClass.equals(aClass))
188                 {
189                     return true;
190                 }
191             }
192             else if (anotherClass.isAssignableFrom(aClass))
193             {
194                 return true;
195             }
196         }
197 
198         return false;
199     }
200 
201     /**
202      * Transforms the object.
203      * 
204      * @param src The source object to transform.
205      * @return The transformed object
206      */
207     public final Object transform(Object src) throws TransformerException
208     {
209         String encoding = null;
210 
211         if (src instanceof UMOMessage && !isSourceTypeSupported(UMOMessage.class))
212         {
213             encoding = ((UMOMessage) src).getEncoding();
214             src = ((UMOMessage) src).getPayload();
215         }
216 
217         if (encoding == null && endpoint != null)
218         {
219             encoding = endpoint.getEncoding();
220         }
221 
222         // last resort
223         if (encoding == null)
224         {
225             encoding = MuleManager.getConfiguration().getEncoding();
226         }
227 
228         if (!isSourceTypeSupported(src.getClass()))
229         {
230             if (ignoreBadInput)
231             {
232                 logger.debug("Source type is incompatible with this transformer and property 'ignoreBadInput' is set to true, so the transformer chain will continue.");
233                 return nextTransform(src);
234             }
235             else
236             {
237                 throw new TransformerException(
238                     CoreMessages.transformOnObjectUnsupportedTypeOfEndpoint(this.getName(), 
239                         src.getClass(), endpoint), this);
240             }
241         }
242 
243         if (logger.isDebugEnabled())
244         {
245             logger.debug("Applying transformer " + getName() + " (" + getClass().getName() + ")");
246             logger.debug("Object before transform: "
247                             + StringMessageUtils.truncate(StringMessageUtils.toString(src), DEFAULT_TRUNCATE_LENGTH, false));
248         }
249 
250         Object result = doTransform(src, encoding);
251         if (result == null)
252         {
253             result = NullPayload.getInstance();
254         }
255 
256         if (logger.isDebugEnabled())
257         {
258             logger.debug("Object after transform: "
259                             + StringMessageUtils.truncate(StringMessageUtils.toString(result), DEFAULT_TRUNCATE_LENGTH, false));
260         }
261 
262         result = checkReturnClass(result);
263 
264         result = nextTransform(result);
265 
266         return result;
267     }
268 
269     public UMOImmutableEndpoint getEndpoint()
270     {
271         return endpoint;
272     }
273 
274     /*
275      * (non-Javadoc)
276      * 
277      * @see org.mule.umo.transformer.UMOTransformer#setConnector(org.mule.umo.provider.UMOConnector)
278      */
279     public void setEndpoint(UMOImmutableEndpoint endpoint)
280     {
281         this.endpoint = endpoint;
282         UMOTransformer trans = nextTransformer;
283         while (trans != null && endpoint != null)
284         {
285             trans.setEndpoint(endpoint);
286             trans = trans.getNextTransformer();
287         }
288     }
289 
290     protected abstract Object doTransform(Object src, String encoding) throws TransformerException;
291 
292     /*
293      * (non-Javadoc)
294      * 
295      * @see org.mule.umo.transformer.UMOTransformer#getNextTransformer()
296      */
297     public UMOTransformer getNextTransformer()
298     {
299         return nextTransformer;
300     }
301 
302     /*
303      * (non-Javadoc)
304      * 
305      * @see org.mule.umo.transformer.UMOTransformer#setNextTransformer(org.mule.umo.transformer.UMOTransformer)
306      */
307     public void setNextTransformer(UMOTransformer nextTransformer)
308     {
309         this.nextTransformer = nextTransformer;
310     }
311 
312     /*
313      * (non-Javadoc)
314      * 
315      * @see java.lang.Object#clone()
316      */
317     public Object clone() throws CloneNotSupportedException
318     {
319         try
320         {
321             /*
322              * Object.clone() is horribly broken, so we create a new instance
323              * manually and let BeanUtils populate all accessible "properties". That
324              * is about the best we can do to fulfill the general clone() contract
325              * (providing a mostly-correct shallow copy out of the box) even though
326              * it is obviously wrong in the face of objects with nontrivial "getter"
327              * methods. It would be much, much better to enforce the use of
328              * copy-constructors, or even better to just *not copy at all*.
329              * UMOTransformer.clone() will apparently go away in 2.0 in favor of
330              * BeanFactory-created instances so there is still hope, though I am
331              * sceptical whether his will fix all aliasing problems. It seems that
332              * the reference cycle between transformers and endpoints and the dynamic
333              * updating of copied transformers with new endpoints is the real root of
334              * the problem.
335              */
336             AbstractTransformer clone = (AbstractTransformer)BeanUtils.cloneBean(this);
337 
338             /*
339              * clear out the cloned list of types since subclass constructors
340              * probably have registered their source types, leading to duplicates..
341              */
342             clone.sourceTypes.clear();
343             clone.sourceTypes.addAll(sourceTypes);
344 
345             // recursively copy any chained transformers
346             if (nextTransformer != null)
347             {
348                 clone.setNextTransformer((UMOTransformer) nextTransformer.clone());
349             }
350 
351             // update all chained transformers to use this endpoint which is *shared*
352             clone.setEndpoint(endpoint);
353 
354             return clone;
355         }
356         catch (Exception e)
357         {
358             throw (CloneNotSupportedException) new CloneNotSupportedException("Failed to clone transformer: "
359                             + e.getMessage()).initCause(e);
360         }
361     }
362 
363     /**
364      * Will return the return type for the last transformer in the chain
365      * 
366      * @return the last transformers return type
367      */
368     public Class getFinalReturnClass()
369     {
370         UMOTransformer tempTrans = this;
371         UMOTransformer returnTrans = this;
372         while (tempTrans != null)
373         {
374             returnTrans = tempTrans;
375             tempTrans = tempTrans.getNextTransformer();
376         }
377         return returnTrans.getReturnClass();
378     }
379 
380     /**
381      * Template method were deriving classes can do any initialisation after the
382      * properties have been set on this transformer
383      * 
384      * @throws InitialisationException
385      */
386     public void initialise() throws InitialisationException
387     {
388         // nothing to do
389     }
390 
391     protected String generateTransformerName()
392     {
393         return ClassUtils.getSimpleName(this.getClass());
394     }
395 
396     public boolean isIgnoreBadInput()
397     {
398         return ignoreBadInput;
399     }
400 
401     public void setIgnoreBadInput(boolean ignoreBadInput)
402     {
403         this.ignoreBadInput = ignoreBadInput;
404     }
405 
406     // @Override
407     public String toString()
408     {
409         StringBuffer sb = new StringBuffer(80);
410         sb.append(ClassUtils.getSimpleName(this.getClass()));
411         sb.append("{this=").append(Integer.toHexString(System.identityHashCode(this)));
412         sb.append(", name='").append(name).append('\'');
413         sb.append(", ignoreBadInput=").append(ignoreBadInput);
414         sb.append(", returnClass=").append(returnClass);
415         sb.append(", sourceTypes=").append(sourceTypes);
416         sb.append('}');
417         return sb.toString();                        
418     }
419 
420     public boolean isAcceptNull()
421     {
422         return false;
423     }
424 
425     /**
426      * Safely call the next transformer in chain, if any.
427      */
428     protected Object nextTransform(Object result)
429             throws TransformerException
430     {
431         if (nextTransformer != null)
432         {
433             logger.debug("Following transformer in the chain is " + nextTransformer.getName() + " ("
434                             + nextTransformer.getClass().getName() + ")");
435             result = nextTransformer.transform(result);
436         }
437         return result;
438     }
439 }