View Javadoc

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