View Javadoc

1   /*
2    * $Id: AbstractTransformer.java 19250 2010-08-30 16:53:14Z dirk.olmes $
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.transformer;
12  
13  import org.mule.api.MuleContext;
14  import org.mule.api.MuleEvent;
15  import org.mule.api.MuleException;
16  import org.mule.api.MuleMessage;
17  import org.mule.api.config.MuleProperties;
18  import org.mule.api.context.notification.MuleContextNotificationListener;
19  import org.mule.api.endpoint.ImmutableEndpoint;
20  import org.mule.api.lifecycle.InitialisationException;
21  import org.mule.api.transformer.DataType;
22  import org.mule.api.transformer.Transformer;
23  import org.mule.api.transformer.TransformerException;
24  import org.mule.api.transformer.TransformerMessagingException;
25  import org.mule.config.i18n.CoreMessages;
26  import org.mule.config.i18n.Message;
27  import org.mule.context.notification.MuleContextNotification;
28  import org.mule.context.notification.NotificationException;
29  import org.mule.transformer.types.DataTypeFactory;
30  import org.mule.transformer.types.SimpleDataType;
31  import org.mule.transport.NullPayload;
32  import org.mule.util.ClassUtils;
33  import org.mule.util.StringMessageUtils;
34  import org.mule.util.StringUtils;
35  
36  import java.io.InputStream;
37  import java.util.ArrayList;
38  import java.util.Collections;
39  import java.util.List;
40  
41  import javax.activation.MimeType;
42  import javax.activation.MimeTypeParseException;
43  import javax.xml.transform.stream.StreamSource;
44  
45  import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArrayList;
46  
47  import org.apache.commons.logging.Log;
48  import org.apache.commons.logging.LogFactory;
49  
50  /**
51   * <code>AbstractTransformer</code> is a base class for all transformers.
52   * Transformations transform one object into another.
53   */
54  
55  public abstract class AbstractTransformer implements Transformer, MuleContextNotificationListener<MuleContextNotification>
56  {
57      public static final DataType<MuleMessage> MULE_MESSAGE_DATA_TYPE = new SimpleDataType<MuleMessage>(MuleMessage.class);
58  
59      protected MuleContext muleContext;
60  
61      protected final Log logger = LogFactory.getLog(getClass());
62  
63      /**
64       * The return type that will be returned by the {@link #transform} method is
65       * called
66       */
67      protected DataType<?> returnType = new SimpleDataType<Object>(Object.class);
68  
69      /**
70       * The name that identifies this transformer. If none is set the class name of
71       * the transformer is used
72       */
73      protected String name = null;
74  
75      /**
76       * The endpoint that this transformer instance is configured on
77       */
78      protected ImmutableEndpoint endpoint = null;
79  
80      /**
81       * A list of supported Class types that the source payload passed into this
82       * transformer
83       */
84      @SuppressWarnings("unchecked")
85      protected final List<DataType<?>> sourceTypes = new CopyOnWriteArrayList/*<DataType>*/();
86  
87      /**
88       * Determines whether the transformer will throw an exception if the message
89       * passed is is not supported
90       */
91      private boolean ignoreBadInput = false;
92  
93      /**
94       * Allows a transformer to return a null result
95       */
96      private boolean allowNullReturn = false;
97  
98      /*
99       *  Mime type and encoding for transformer output
100      */
101     protected String mimeType;
102     protected String encoding;
103 
104     /**
105      * default constructor required for discovery
106      */
107     public AbstractTransformer()
108     {
109         super();
110     }
111 
112     public MuleEvent process(MuleEvent event) throws MuleException
113     {
114         if (event != null && event.getMessage() != null)
115         {
116             try
117             {
118                 event.getMessage().applyTransformers(event, this);
119             }
120             catch (TransformerException e)
121             {
122                 throw new TransformerMessagingException(e.getI18nMessage(), event, this, e);
123             }
124         }
125         return event;
126     }
127     
128     protected Object checkReturnClass(Object object) throws TransformerException
129     {
130         //Null is a valid return type
131         if(object==null || object instanceof NullPayload && isAllowNullReturn())
132         {
133             return object;
134         }
135 
136         if (returnType != null)
137         {
138             DataType<?> dt = DataTypeFactory.create(object.getClass());
139             if (!returnType.isCompatibleWith(dt))
140             {
141                 throw new TransformerException(
142                         CoreMessages.transformUnexpectedType(dt, returnType),
143                         this);
144             }
145         }
146 
147         if (logger.isDebugEnabled())
148         {
149             logger.debug("The transformed object is of expected type. Type is: " +
150                     ClassUtils.getSimpleName(object.getClass()));
151         }
152 
153         return object;
154     }
155 
156     /**
157      * Register a supported data type with this transformer.  The will allow objects that match this data type to be
158      * transformed by this transformer.
159      *
160      * @param aClass the source type to allow
161      * @deprecated use registerSourceType(DataType)
162      */
163     @Deprecated
164     protected void registerSourceType(Class<?> aClass)
165     {
166         registerSourceType(new SimpleDataType<Object>(aClass));
167     }
168 
169     /**
170      * Unregister a supported source type from this transformer
171      *
172      * @param aClass the type to remove
173      * @deprecated use unregisterSourceType(DataType)
174      */
175     @Deprecated
176     protected void unregisterSourceType(Class<?> aClass)
177     {
178         unregisterSourceType(new SimpleDataType<Object>(aClass));
179     }
180 
181     /**
182      * Register a supported data type with this transformer.  The will allow objects that match this data type to be
183      * transformed by this transformer.
184      *
185      * @param dataType the source type to allow
186      */
187     protected void registerSourceType(DataType<?> dataType)
188     {
189         if (!sourceTypes.contains(dataType))
190         {
191             sourceTypes.add(dataType);
192 
193             if (dataType.getType().equals(Object.class))
194             {
195                 logger.debug("java.lang.Object has been added as source type for this transformer, there will be no source type checking performed");
196             }
197         }
198     }
199 
200     /**
201      * Unregister a supported source type from this transformer
202      *
203      * @param dataType the type to remove
204      */
205     protected void unregisterSourceType(DataType<?> dataType)
206     {
207         sourceTypes.remove(dataType);
208     }
209 
210     /**
211      * @return transformer name
212      */
213     public String getName()
214     {
215         if (name == null)
216         {
217             name = this.generateTransformerName();
218         }
219         return name;
220     }
221 
222     /**
223      * @param string
224      */
225     public void setName(String string)
226     {
227         if (string == null)
228         {
229             string = ClassUtils.getSimpleName(this.getClass());
230         }
231 
232         logger.debug("Setting transformer name to: " + string);
233         name = string;
234     }
235 
236     @Deprecated
237     public Class<?> getReturnClass()
238     {
239         return returnType.getType();
240     }
241 
242     public void setReturnDataType(DataType<?> type)
243     {
244         this.returnType = type.cloneDataType();
245         this.encoding = type.getEncoding();
246         this.mimeType = type.getMimeType();
247     }
248 
249     public DataType<?> getReturnDataType()
250     {
251         return returnType;
252     }
253 
254     @Deprecated
255     public void setReturnClass(Class<?> newClass)
256     {
257         returnType = new SimpleDataType<Object>(newClass);
258         returnType.setMimeType(mimeType);
259         returnType.setEncoding(encoding);
260     }
261 
262     public void setMimeType(String mimeType) throws MimeTypeParseException
263     {
264         if (mimeType == null)
265         {
266             this.mimeType = null;
267         }
268         else
269         {
270             MimeType mt = new MimeType(mimeType);
271             this.mimeType = mt.getPrimaryType() + "/" + mt.getSubType();
272         }
273         if (returnType != null)
274         {
275             returnType.setMimeType(mimeType);
276         }
277     }
278 
279     public String getMimeType()
280     {
281         return mimeType;
282     }
283 
284     public String getEncoding()
285     {
286         return encoding;
287     }
288 
289     public void setEncoding(String encoding)
290     {
291         this.encoding = encoding;
292         if (returnType != null)
293         {
294             returnType.setEncoding(encoding);
295         }
296     }
297 
298     public boolean isAllowNullReturn()
299     {
300         return allowNullReturn;
301     }
302 
303     public void setAllowNullReturn(boolean allowNullReturn)
304     {
305         this.allowNullReturn = allowNullReturn;
306     }
307 
308     @Deprecated
309     public boolean isSourceTypeSupported(Class<?> aClass)
310     {
311         return isSourceDataTypeSupported(DataTypeFactory.create(aClass), false);
312     }
313 
314     public boolean isSourceDataTypeSupported(DataType<?> dataType)
315     {
316         return isSourceDataTypeSupported(dataType, false);
317     }
318 
319     /**
320      * Determines whether that data type passed in is supported by this transformer
321      *
322      * @param aClass     the type to check against
323      * @param exactMatch if the source type on this transformer is open (can be anything) it will return true unless an
324      *                   exact match is requested using this flag
325      * @return true if the source type is supported by this transformer, false otherwise
326      * @deprecated use {@link #isSourceDataTypeSupported(org.mule.api.transformer.DataType, boolean)}
327      */
328     @Deprecated
329     public boolean isSourceTypeSupported(Class<MuleMessage> aClass, boolean exactMatch)
330     {
331         return isSourceDataTypeSupported(new SimpleDataType<MuleMessage>(aClass), exactMatch);
332     }
333 
334     /**
335      * Determines whether that data type passed in is supported by this transformer
336      *
337      * @param dataType   the type to check against
338      * @param exactMatch if set to true, this method will look for an exact match to the data type, if false it will look
339      *                   for a compatible data type.
340      * @return true if the source type is supported by this transformer, false otherwise
341      */
342     public boolean isSourceDataTypeSupported(DataType<?> dataType, boolean exactMatch)
343     {
344         int numTypes = sourceTypes.size();
345 
346         if (numTypes == 0)
347         {
348             return !exactMatch;
349         }
350 
351         for (DataType<?> sourceType : sourceTypes)
352         {
353             if (exactMatch)
354             {
355                 if (sourceType.equals(dataType))
356                 {
357                     return true;
358                 }
359             }
360             else
361             {
362                 if (sourceType.isCompatibleWith(dataType))
363                 {
364                     return true;
365                 }
366             }
367         }
368         return false;
369     }
370 
371     public final Object transform(Object src) throws TransformerException
372     {
373         return transform(src, getEncoding(src));
374     }
375 
376     public Object transform(Object src, String enc) throws TransformerException
377     {
378         Object payload = src;
379         if (src instanceof MuleMessage)
380         {
381             MuleMessage message = (MuleMessage) src;
382             if ((!isSourceDataTypeSupported(MULE_MESSAGE_DATA_TYPE, true) &&
383                  !(this instanceof AbstractMessageTransformer)))
384             {
385                 src = ((MuleMessage) src).getPayload();
386                 payload = message.getPayload();
387             }
388         }
389 
390         DataType<?> sourceType = DataTypeFactory.create(payload.getClass());
391         //Once we support mime types, it should be possible to do this since we'll be able to discern the difference
392         //between objects with the same type
393 //        if(getReturnDataType().isCompatibleWith(sourceType))
394 //        {
395 //            logger.debug("Object is already of type: " + getReturnDataType() + " No transform to perform");
396 //            return payload;
397 //        }
398 
399         if (!isSourceDataTypeSupported(sourceType))
400         {
401             if (ignoreBadInput)
402             {
403                 logger.debug("Source type is incompatible with this transformer and property 'ignoreBadInput' is set to true, so the transformer chain will continue.");
404                 return payload;
405             }
406             else
407             {
408                 Message msg = CoreMessages.transformOnObjectUnsupportedTypeOfEndpoint(getName(),
409                     payload.getClass(), endpoint);
410                 /// FIXME
411                 throw new TransformerException(msg, this);
412             }
413         }
414 
415         if (logger.isDebugEnabled())
416         {
417             logger.debug(String.format("Applying transformer %s (%s)", getName(), getClass().getName()));
418             logger.debug(String.format("Object before transform: %s", StringMessageUtils.toString(payload)));
419         }
420 
421         Object result = doTransform(payload, enc);
422 
423         if (result == null)
424         {
425             result = NullPayload.getInstance();
426         }
427 
428         if (logger.isDebugEnabled())
429         {
430             logger.debug(String.format("Object after transform: %s", StringMessageUtils.toString(result)));
431         }
432 
433         result = checkReturnClass(result);
434         return result;
435     }
436 
437     protected String getEncoding(Object src)
438     {
439         String enc = null;
440         if (src instanceof MuleMessage)
441         {
442             enc = ((MuleMessage) src).getEncoding();
443         }
444 
445         if (enc == null && endpoint != null)
446         {
447             enc = endpoint.getEncoding();
448         }
449         else if (enc == null)
450         {
451             enc = System.getProperty(MuleProperties.MULE_ENCODING_SYSTEM_PROPERTY);
452         }
453         return enc;
454     }
455 
456     protected boolean isConsumed(Class<?> srcCls)
457     {
458         return InputStream.class.isAssignableFrom(srcCls) || StreamSource.class.isAssignableFrom(srcCls);
459     }
460 
461     public ImmutableEndpoint getEndpoint()
462     {
463         return endpoint;
464     }
465 
466     public void setEndpoint(ImmutableEndpoint endpoint)
467     {
468         this.endpoint = endpoint;
469     }
470 
471     protected abstract Object doTransform(Object src, String enc) throws TransformerException;
472 
473     /**
474      * Template method where deriving classes can do any initialisation after the
475      * properties have been set on this transformer
476      *
477      * @throws InitialisationException
478      */
479     public void initialise() throws InitialisationException
480     {
481         // do nothing, subclasses may override
482     }
483 
484     /**
485      * Template method where deriving classes can do any clean up any resources or state
486      * before the object is disposed.
487      */
488     public void dispose()
489     {
490         // do nothing, subclasses may override
491     }
492 
493     protected String generateTransformerName()
494     {
495         String transformerName = ClassUtils.getSimpleName(this.getClass());
496         int i = transformerName.indexOf("To");
497         if (i > 0 && returnType != null)
498         {
499             String target = ClassUtils.getSimpleName(returnType.getType());
500             if (target.equals("byte[]"))
501             {
502                 target = "byteArray";
503             }
504             transformerName = transformerName.substring(0, i + 2) + StringUtils.capitalize(target);
505         }
506         return transformerName;
507     }
508 
509     @Deprecated
510     public List<Class<?>> getSourceTypes()
511     {
512         //A work around to support the legacy API
513         List<Class<?>> sourceClasses = new ArrayList<Class<?>>();
514         for (DataType<?> sourceType : sourceTypes)
515         {
516             sourceClasses.add(sourceType.getType());
517         }
518         return Collections.unmodifiableList(sourceClasses);
519     }
520 
521     public List<DataType<?>> getSourceDataTypes()
522     {
523         return Collections.unmodifiableList(sourceTypes);
524     }
525 
526     public boolean isIgnoreBadInput()
527     {
528         return ignoreBadInput;
529     }
530 
531     public void setIgnoreBadInput(boolean ignoreBadInput)
532     {
533         this.ignoreBadInput = ignoreBadInput;
534     }
535 
536     @Override
537     public String toString()
538     {
539         StringBuffer sb = new StringBuffer(80);
540         sb.append(ClassUtils.getSimpleName(this.getClass()));
541         sb.append("{this=").append(Integer.toHexString(System.identityHashCode(this)));
542         sb.append(", name='").append(name).append('\'');
543         sb.append(", ignoreBadInput=").append(ignoreBadInput);
544         sb.append(", returnClass=").append(returnType);
545         sb.append(", sourceTypes=").append(sourceTypes);
546         sb.append('}');
547         return sb.toString();
548     }
549 
550     public boolean isAcceptNull()
551     {
552         return false;
553     }
554 
555     public void setMuleContext(MuleContext context)
556     {
557         this.muleContext = context;
558         try
559         {
560             muleContext.registerListener(this);
561         }
562         catch (NotificationException e)
563         {
564             logger.error("failed to register context listener", e);
565         }
566     }
567 
568     public void onNotification(MuleContextNotification notification)
569     {
570         if (notification.getAction() == MuleContextNotification.CONTEXT_DISPOSING)
571         {
572             this.dispose();
573         }
574     }
575 }