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