View Javadoc

1   /*
2    * $Id: AbstractTransformer.java 20080 2010-11-05 04:00:22Z dfeist $
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         DataType<?> tempReturnType = new SimpleDataType<Object>(newClass);
258         tempReturnType.setMimeType(mimeType);
259         tempReturnType.setEncoding(encoding);
260         setReturnDataType(tempReturnType);
261     }
262 
263     public void setMimeType(String mimeType) throws MimeTypeParseException
264     {
265         if (mimeType == null)
266         {
267             this.mimeType = null;
268         }
269         else
270         {
271             MimeType mt = new MimeType(mimeType);
272             this.mimeType = mt.getPrimaryType() + "/" + mt.getSubType();
273         }
274         if (returnType != null)
275         {
276             returnType.setMimeType(mimeType);
277         }
278     }
279 
280     public String getMimeType()
281     {
282         return mimeType;
283     }
284 
285     public String getEncoding()
286     {
287         return encoding;
288     }
289 
290     public void setEncoding(String encoding)
291     {
292         this.encoding = encoding;
293         if (returnType != null)
294         {
295             returnType.setEncoding(encoding);
296         }
297     }
298 
299     public boolean isAllowNullReturn()
300     {
301         return allowNullReturn;
302     }
303 
304     public void setAllowNullReturn(boolean allowNullReturn)
305     {
306         this.allowNullReturn = allowNullReturn;
307     }
308 
309     @Deprecated
310     public boolean isSourceTypeSupported(Class<?> aClass)
311     {
312         return isSourceDataTypeSupported(DataTypeFactory.create(aClass), false);
313     }
314 
315     public boolean isSourceDataTypeSupported(DataType<?> dataType)
316     {
317         return isSourceDataTypeSupported(dataType, false);
318     }
319 
320     /**
321      * Determines whether that data type passed in is supported by this transformer
322      *
323      * @param aClass     the type to check against
324      * @param exactMatch if the source type on this transformer is open (can be anything) it will return true unless an
325      *                   exact match is requested using this flag
326      * @return true if the source type is supported by this transformer, false otherwise
327      * @deprecated use {@link #isSourceDataTypeSupported(org.mule.api.transformer.DataType, boolean)}
328      */
329     @Deprecated
330     public boolean isSourceTypeSupported(Class<MuleMessage> aClass, boolean exactMatch)
331     {
332         return isSourceDataTypeSupported(new SimpleDataType<MuleMessage>(aClass), exactMatch);
333     }
334 
335     /**
336      * Determines whether that data type passed in is supported by this transformer
337      *
338      * @param dataType   the type to check against
339      * @param exactMatch if set to true, this method will look for an exact match to the data type, if false it will look
340      *                   for a compatible data type.
341      * @return true if the source type is supported by this transformer, false otherwise
342      */
343     public boolean isSourceDataTypeSupported(DataType<?> dataType, boolean exactMatch)
344     {
345         int numTypes = sourceTypes.size();
346 
347         if (numTypes == 0)
348         {
349             return !exactMatch;
350         }
351 
352         for (DataType<?> sourceType : sourceTypes)
353         {
354             if (exactMatch)
355             {
356                 if (sourceType.equals(dataType))
357                 {
358                     return true;
359                 }
360             }
361             else
362             {
363                 if (sourceType.isCompatibleWith(dataType))
364                 {
365                     return true;
366                 }
367             }
368         }
369         return false;
370     }
371 
372     public final Object transform(Object src) throws TransformerException
373     {
374         return transform(src, getEncoding(src));
375     }
376 
377     public Object transform(Object src, String enc) throws TransformerException
378     {
379         Object payload = src;
380         if (src instanceof MuleMessage)
381         {
382             MuleMessage message = (MuleMessage) src;
383             if ((!isSourceDataTypeSupported(MULE_MESSAGE_DATA_TYPE, true) &&
384                  !(this instanceof AbstractMessageTransformer)))
385             {
386                 src = ((MuleMessage) src).getPayload();
387                 payload = message.getPayload();
388             }
389         }
390 
391         DataType<?> sourceType = DataTypeFactory.create(payload.getClass());
392         //Once we support mime types, it should be possible to do this since we'll be able to discern the difference
393         //between objects with the same type
394 //        if(getReturnDataType().isCompatibleWith(sourceType))
395 //        {
396 //            logger.debug("Object is already of type: " + getReturnDataType() + " No transform to perform");
397 //            return payload;
398 //        }
399 
400         if (!isSourceDataTypeSupported(sourceType))
401         {
402             if (ignoreBadInput)
403             {
404                 logger.debug("Source type is incompatible with this transformer and property 'ignoreBadInput' is set to true, so the transformer chain will continue.");
405                 return payload;
406             }
407             else
408             {
409                 Message msg = CoreMessages.transformOnObjectUnsupportedTypeOfEndpoint(getName(),
410                     payload.getClass(), endpoint);
411                 /// FIXME
412                 throw new TransformerException(msg, this);
413             }
414         }
415 
416         if (logger.isDebugEnabled())
417         {
418             logger.debug(String.format("Applying transformer %s (%s)", getName(), getClass().getName()));
419             logger.debug(String.format("Object before transform: %s", StringMessageUtils.toString(payload)));
420         }
421 
422         Object result = doTransform(payload, enc);
423 
424         if (result == null)
425         {
426             result = NullPayload.getInstance();
427         }
428 
429         if (logger.isDebugEnabled())
430         {
431             logger.debug(String.format("Object after transform: %s", StringMessageUtils.toString(result)));
432         }
433 
434         result = checkReturnClass(result);
435         return result;
436     }
437 
438     protected String getEncoding(Object src)
439     {
440         String enc = null;
441         if (src instanceof MuleMessage)
442         {
443             enc = ((MuleMessage) src).getEncoding();
444         }
445 
446         if (enc == null && endpoint != null)
447         {
448             enc = endpoint.getEncoding();
449         }
450         else if (enc == null)
451         {
452             enc = System.getProperty(MuleProperties.MULE_ENCODING_SYSTEM_PROPERTY);
453         }
454         return enc;
455     }
456 
457     protected boolean isConsumed(Class<?> srcCls)
458     {
459         return InputStream.class.isAssignableFrom(srcCls) || StreamSource.class.isAssignableFrom(srcCls);
460     }
461 
462     public ImmutableEndpoint getEndpoint()
463     {
464         return endpoint;
465     }
466 
467     public void setEndpoint(ImmutableEndpoint endpoint)
468     {
469         this.endpoint = endpoint;
470     }
471 
472     protected abstract Object doTransform(Object src, String enc) throws TransformerException;
473 
474     /**
475      * Template method where deriving classes can do any initialisation after the
476      * properties have been set on this transformer
477      *
478      * @throws InitialisationException
479      */
480     public void initialise() throws InitialisationException
481     {
482         // do nothing, subclasses may override
483     }
484 
485     /**
486      * Template method where deriving classes can do any clean up any resources or state
487      * before the object is disposed.
488      */
489     public void dispose()
490     {
491         // do nothing, subclasses may override
492     }
493 
494     protected String generateTransformerName()
495     {
496         String transformerName = ClassUtils.getSimpleName(this.getClass());
497         int i = transformerName.indexOf("To");
498         if (i > 0 && returnType != null)
499         {
500             String target = ClassUtils.getSimpleName(returnType.getType());
501             if (target.equals("byte[]"))
502             {
503                 target = "byteArray";
504             }
505             transformerName = transformerName.substring(0, i + 2) + StringUtils.capitalize(target);
506         }
507         return transformerName;
508     }
509 
510     @Deprecated
511     public List<Class<?>> getSourceTypes()
512     {
513         //A work around to support the legacy API
514         List<Class<?>> sourceClasses = new ArrayList<Class<?>>();
515         for (DataType<?> sourceType : sourceTypes)
516         {
517             sourceClasses.add(sourceType.getType());
518         }
519         return Collections.unmodifiableList(sourceClasses);
520     }
521 
522     public List<DataType<?>> getSourceDataTypes()
523     {
524         return Collections.unmodifiableList(sourceTypes);
525     }
526 
527     public boolean isIgnoreBadInput()
528     {
529         return ignoreBadInput;
530     }
531 
532     public void setIgnoreBadInput(boolean ignoreBadInput)
533     {
534         this.ignoreBadInput = ignoreBadInput;
535     }
536 
537     @Override
538     public String toString()
539     {
540         StringBuffer sb = new StringBuffer(80);
541         sb.append(ClassUtils.getSimpleName(this.getClass()));
542         sb.append("{this=").append(Integer.toHexString(System.identityHashCode(this)));
543         sb.append(", name='").append(name).append('\'');
544         sb.append(", ignoreBadInput=").append(ignoreBadInput);
545         sb.append(", returnClass=").append(returnType);
546         sb.append(", sourceTypes=").append(sourceTypes);
547         sb.append('}');
548         return sb.toString();
549     }
550 
551     public boolean isAcceptNull()
552     {
553         return false;
554     }
555 
556     public void setMuleContext(MuleContext context)
557     {
558         this.muleContext = context;
559         try
560         {
561             muleContext.registerListener(this);
562         }
563         catch (NotificationException e)
564         {
565             logger.error("failed to register context listener", e);
566         }
567     }
568 
569     public void onNotification(MuleContextNotification notification)
570     {
571         if (notification.getAction() == MuleContextNotification.CONTEXT_DISPOSING)
572         {
573             this.dispose();
574         }
575     }
576 }