View Javadoc

1   /*
2    * $Id: AbstractMessageAdapter.java 7963 2007-08-21 08:53:15Z dirk.olmes $
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.providers;
12  
13  import org.mule.MuleManager;
14  import org.mule.MuleRuntimeException;
15  import org.mule.config.MuleProperties;
16  import org.mule.config.i18n.CoreMessages;
17  import org.mule.impl.ThreadSafeAccess;
18  import org.mule.umo.UMOExceptionPayload;
19  import org.mule.umo.provider.UMOMessageAdapter;
20  import org.mule.umo.transformer.TransformerException;
21  import org.mule.util.MapUtils;
22  import org.mule.util.StringUtils;
23  import org.mule.util.UUID;
24  
25  import java.io.Serializable;
26  import java.io.UnsupportedEncodingException;
27  import java.util.Collections;
28  import java.util.Iterator;
29  import java.util.Map;
30  import java.util.Set;
31  
32  import javax.activation.DataHandler;
33  
34  import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
35  import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentMap;
36  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
37  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicReference;
38  import org.apache.commons.lang.SerializationUtils;
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  
42  /**
43   * <code>AbstractMessageAdapter</code> provides a base implementation for simple
44   * message types that maybe don't normally allow for meta information, such as a File
45   * or TCP.
46   */
47  public abstract class AbstractMessageAdapter implements UMOMessageAdapter, ThreadSafeAccess
48  {
49  
50      /**
51       * logger used by this class
52       */
53      protected transient Log logger = LogFactory.getLog(getClass());
54  
55      protected ConcurrentMap properties = new ConcurrentHashMap();
56      protected ConcurrentMap attachments = new ConcurrentHashMap();
57      protected String encoding = MuleManager.getConfiguration().getEncoding();
58  
59      /**
60       * Should we fail when we detect scribbling?  This can be overridden by setting the
61       * property {@link org.mule.config.MuleProperties#MULE_THREAD_UNSAFE_MESSAGES_PROPERTY}
62       */
63      public static final boolean DEFAULT_FAILFAST = false;
64  
65      protected UMOExceptionPayload exceptionPayload;
66      protected String id = UUID.getUUID();
67  
68      // these are transient because serisalisation generates a new instance
69      // so we allow mutation again (and we can't serialize threads anyway)
70      private transient AtomicReference ownerThread = null;
71      private transient AtomicBoolean mutable = null;
72      public static final boolean WRITE = true;
73      public static final boolean READ = false;
74  
75      protected AbstractMessageAdapter()
76      {
77          // usual access for subclasses
78      }
79  
80      protected AbstractMessageAdapter(UMOMessageAdapter template)
81      {
82          if (null != template)
83          {
84              Iterator propertyNames = template.getPropertyNames().iterator();
85              while (propertyNames.hasNext())
86              {
87                  String key = (String) propertyNames.next();
88                  try
89                  {
90                     setProperty(key, template.getProperty(key));
91                  }
92                  catch (Exception e)
93                  {
94                      throw new MuleRuntimeException(CoreMessages.failedToReadPayload(), e);
95                  }
96              }
97              Iterator attachmentNames = template.getAttachmentNames().iterator();
98              while (attachmentNames.hasNext())
99              {
100                 String key = (String) attachmentNames.next();
101                 try
102                 {
103                     addAttachment(key, template.getAttachment(key));
104                 }
105                 catch (Exception e)
106                 {
107                     throw new MuleRuntimeException(CoreMessages.failedToReadPayload(), e);
108                 }
109             }
110             encoding = template.getEncoding();
111             exceptionPayload = template.getExceptionPayload();
112             id = template.getUniqueId();
113         }
114     }
115 
116     public String toString()
117     {
118         assertAccess(READ);
119         StringBuffer buf = new StringBuffer(120);
120         buf.append(getClass().getName());
121         buf.append("/" + super.toString());
122         buf.append('{');
123         buf.append("id=").append(getUniqueId());
124         buf.append(", payload=").append(getPayload().getClass().getName());
125         buf.append(", correlationId=").append(getCorrelationId());
126         buf.append(", correlationGroup=").append(getCorrelationGroupSize());
127         buf.append(", correlationSeq=").append(getCorrelationSequence());
128         buf.append(", encoding=").append(getEncoding());
129         buf.append(", exceptionPayload=").append(exceptionPayload);
130         buf.append(", properties=").append(MapUtils.toString(properties, true));
131         buf.append('}');
132         return buf.toString();
133     }
134 
135     public void addProperties(Map props)
136     {
137         assertAccess(WRITE);
138         if (props != null)
139         {
140             synchronized (props)
141             {
142                 for (Iterator iter = props.entrySet().iterator(); iter.hasNext();)
143                 {
144                     Map.Entry entry = (Map.Entry) iter.next();
145                     setProperty((String) entry.getKey(), entry.getValue());
146                 }
147             }
148         }
149     }
150 
151     public void clearProperties()
152     {
153         assertAccess(WRITE);
154         properties.clear();
155     }
156 
157     /**
158      * Removes an associated property from the message
159      * 
160      * @param key the key of the property to remove
161      */
162     public Object removeProperty(String key)
163     {
164         assertAccess(WRITE);
165         return properties.remove(key);
166     }
167 
168     /*
169      * (non-Javadoc)
170      * 
171      * @see org.mule.providers.UMOMessageAdapter#getProperty(java.lang.Object)
172      */
173     public Object getProperty(String key)
174     {
175         assertAccess(READ);
176         return properties.get(key);
177     }
178 
179     /*
180      * (non-Javadoc)
181      * 
182      * @see org.mule.providers.UMOMessageAdapter#getPropertyNames()
183      */
184     public Set getPropertyNames()
185     {
186         assertAccess(READ);
187         return Collections.unmodifiableSet(properties.keySet());
188     }
189 
190     /*
191      * (non-Javadoc)
192      * 
193      * @see org.mule.providers.UMOMessageAdapter#setProperty(java.lang.Object,
194      *      java.lang.Object)
195      */
196     public void setProperty(String key, Object value)
197     {
198         assertAccess(WRITE);
199         if (key != null)
200         {
201             if (value != null)
202             {
203                 properties.put(key, value);
204             }
205             else
206             {
207                 logger.warn("setProperty(key, value) called with null value; removing key: " + key
208                             + "; please report the following stack trace to dev@mule.codehaus.org.",
209                     new Throwable());
210                 properties.remove(key);
211             }
212         }
213         else
214         {
215             logger.warn("setProperty(key, value) ignored because of null key for object: " + value
216                         + "; please report the following stack trace to dev@mule.codehaus.org.",
217                 new Throwable());
218         }
219     }
220 
221     public String getUniqueId()
222     {
223         assertAccess(READ);
224         return id;
225     }
226 
227     public Object getProperty(String name, Object defaultValue)
228     {
229         assertAccess(READ);
230         return MapUtils.getObject(properties, name, defaultValue);
231     }
232 
233     public int getIntProperty(String name, int defaultValue)
234     {
235         assertAccess(READ);
236         return MapUtils.getIntValue(properties, name, defaultValue);
237     }
238 
239     public long getLongProperty(String name, long defaultValue)
240     {
241         assertAccess(READ);
242         return MapUtils.getLongValue(properties, name, defaultValue);
243     }
244 
245     public double getDoubleProperty(String name, double defaultValue)
246     {
247         assertAccess(READ);
248         return MapUtils.getDoubleValue(properties, name, defaultValue);
249     }
250 
251     public boolean getBooleanProperty(String name, boolean defaultValue)
252     {
253         assertAccess(READ);
254         return MapUtils.getBooleanValue(properties, name, defaultValue);
255     }
256 
257     public String getStringProperty(String name, String defaultValue)
258     {
259         assertAccess(READ);
260         return MapUtils.getString(properties, name, defaultValue);
261     }
262 
263     public void setBooleanProperty(String name, boolean value)
264     {
265         assertAccess(WRITE);
266         setProperty(name, Boolean.valueOf(value));
267     }
268 
269     public void setIntProperty(String name, int value)
270     {
271         assertAccess(WRITE);
272         setProperty(name, new Integer(value));
273     }
274 
275     public void setLongProperty(String name, long value)
276     {
277         assertAccess(WRITE);
278         setProperty(name, new Long(value));
279     }
280 
281     public void setDoubleProperty(String name, double value)
282     {
283         assertAccess(WRITE);
284         setProperty(name, new Double(value));
285     }
286 
287     public void setStringProperty(String name, String value)
288     {
289         assertAccess(WRITE);
290         setProperty(name, value);
291     }
292 
293     public Object getReplyTo()
294     {
295         assertAccess(READ);
296         return getProperty(MuleProperties.MULE_REPLY_TO_PROPERTY);
297     }
298 
299     public void setReplyTo(Object replyTo)
300     {
301         assertAccess(WRITE);
302         if (replyTo != null)
303         {
304             setProperty(MuleProperties.MULE_REPLY_TO_PROPERTY, replyTo);
305         }
306         else
307         {
308             removeProperty(MuleProperties.MULE_REPLY_TO_PROPERTY);
309         }
310     }
311 
312     public String getCorrelationId()
313     {
314         assertAccess(READ);
315         return (String) getProperty(MuleProperties.MULE_CORRELATION_ID_PROPERTY);
316     }
317 
318     public void setCorrelationId(String correlationId)
319     {
320         assertAccess(WRITE);
321         if (StringUtils.isNotBlank(correlationId))
322         {
323             setProperty(MuleProperties.MULE_CORRELATION_ID_PROPERTY, correlationId);
324         }
325         else
326         {
327             removeProperty(MuleProperties.MULE_CORRELATION_ID_PROPERTY);
328         }
329     }
330 
331     /**
332      * Gets the sequence or ordering number for this message in the the correlation
333      * group (as defined by the correlationId)
334      * 
335      * @return the sequence number or -1 if the sequence is not important
336      */
337     public int getCorrelationSequence()
338     {
339         assertAccess(READ);
340         return getIntProperty(MuleProperties.MULE_CORRELATION_SEQUENCE_PROPERTY, -1);
341     }
342 
343     /**
344      * Gets the sequence or ordering number for this message in the the correlation
345      * group (as defined by the correlationId)
346      * 
347      * @param sequence the sequence number or -1 if the sequence is not important
348      */
349     public void setCorrelationSequence(int sequence)
350     {
351         assertAccess(WRITE);
352         setIntProperty(MuleProperties.MULE_CORRELATION_SEQUENCE_PROPERTY, sequence);
353     }
354 
355     /**
356      * Determines how many messages are in the correlation group
357      * 
358      * @return total messages in this group or -1 if the size is not known
359      */
360     public int getCorrelationGroupSize()
361     {
362         assertAccess(READ);
363         return getIntProperty(MuleProperties.MULE_CORRELATION_GROUP_SIZE_PROPERTY, -1);
364     }
365 
366     /**
367      * Determines how many messages are in the correlation group
368      * 
369      * @param size the total messages in this group or -1 if the size is not known
370      */
371     public void setCorrelationGroupSize(int size)
372     {
373         assertAccess(WRITE);
374         setIntProperty(MuleProperties.MULE_CORRELATION_GROUP_SIZE_PROPERTY, size);
375     }
376 
377     public UMOExceptionPayload getExceptionPayload()
378     {
379         assertAccess(READ);
380         return exceptionPayload;
381     }
382 
383     public void setExceptionPayload(UMOExceptionPayload payload)
384     {
385         assertAccess(WRITE);
386         exceptionPayload = payload;
387     }
388 
389     public void addAttachment(String name, DataHandler dataHandler) throws Exception
390     {
391         assertAccess(WRITE);
392         attachments.put(name, dataHandler);
393     }
394 
395     public void removeAttachment(String name) throws Exception
396     {
397         assertAccess(WRITE);
398         attachments.remove(name);
399     }
400 
401     public DataHandler getAttachment(String name)
402     {
403         assertAccess(READ);
404         return (DataHandler) attachments.get(name);
405     }
406 
407     public Set getAttachmentNames()
408     {
409         assertAccess(READ);
410         return Collections.unmodifiableSet(attachments.keySet());
411     }
412 
413     public String getEncoding()
414     {
415         assertAccess(READ);
416         return encoding;
417     }
418 
419     /**
420      * Sets the encoding for this message
421      * 
422      * @param encoding the encoding to use
423      */
424     public void setEncoding(String encoding)
425     {
426         assertAccess(WRITE);
427         this.encoding = encoding;
428     }
429 
430     /**
431      * Converts the message implementation into a String representation. If encoding
432      * is required it will use the encoding set on the message
433      * 
434      * @return String representation of the message payload
435      * @throws Exception Implementation may throw an endpoint specific exception
436      */
437     public final String getPayloadAsString() throws Exception
438     {
439         assertAccess(READ);
440         return getPayloadAsString(getEncoding());
441     }
442 
443     protected byte[] convertToBytes(Object object) throws TransformerException, UnsupportedEncodingException
444     {
445         assertAccess(READ);
446         if (object instanceof String)
447         {
448             return object.toString().getBytes(getEncoding());
449         }
450 
451         if (object instanceof byte[])
452         {
453             return (byte[]) object;
454         }
455         else if (object instanceof Serializable)
456         {
457             try
458             {
459                 return SerializationUtils.serialize((Serializable) object);
460             }
461             catch (Exception e)
462             {
463                 throw new TransformerException(
464                     CoreMessages.transformFailed(object.getClass().getName(), "byte[]"), e);
465             }
466         }
467         else
468         {
469             throw new TransformerException(
470                 CoreMessages.transformOnObjectNotOfSpecifiedType(object.getClass().getName(), 
471                     "byte[] or " + Serializable.class.getName()));
472         }
473     }
474 
475     /**
476      * Restrict mutation to private use within a single thread.
477      * Allow reading and writing by initial thread only.
478      * Once accessed by another thread, no writing allowed at all.
479      *
480      * @param write
481      */
482     public void assertAccess(boolean write)
483     {
484         initAccessControl();
485         setOwner();
486         checkMutable(write);
487     }
488 
489     private void setOwner()
490     {
491         if (null == ownerThread.get())
492         {
493             ownerThread.compareAndSet(null, Thread.currentThread());
494         }
495     }
496 
497     private void checkMutable(boolean write)
498     {
499 
500         // IF YOU SEE AN EXCEPTION THAT IS RAISED FROM WITHIN THIS CODE
501         // ============================================================
502         //
503         // First, understand that the exception here is not the "real" problem.  These exceptions
504         // give early warning of a much more serious issue that results in unreliable and unpredictable
505         // code - more than one thread is attempting to change the contents of a message.
506         //
507         // Having said that, you can disable these exceptions by defining
508         // MuleProperties.MULE_THREAD_UNSAFE_MESSAGES_PROPERTY (org.mule.disable.threadsafemessages)
509         // (ie by adding -Dorg.mule.disable.threadsafemessages=true to the java command line).
510         //
511         // To remove the underlying cause, however, you probably need to do one of:
512         //
513         // - make sure that the message adapter you are using correclty implements the
514         // ThreadSafeAccess interface
515         //
516         // - make sure that dispatcher and receiver classes copy ThreadSafeAccess instances when
517         // they are passed between threads
518 
519         Thread currentThread = Thread.currentThread();
520         if (currentThread.equals(ownerThread.get()))
521         {
522             if (write && !mutable.get())
523             {
524                 if (isDisabled())
525                 {
526                     logger.warn("Writing to immutable message (exception disabled)");
527                 }
528                 else
529                 {
530                     throw newException("Cannot write to immutable message");
531                 }
532             }
533         }
534         else
535         {
536             if (write)
537             {
538                 if (isDisabled())
539                 {
540                     logger.warn("Non-owner writing to message (exception disabled)");
541                 }
542                 else
543                 {
544                     throw newException("Only owner thread can write to message: "
545                             + ownerThread.get() + "/" + Thread.currentThread());
546                 }
547             }
548             else
549             {
550                 // access by another thread
551                 mutable.set(false);
552             }
553         }
554     }
555 
556     protected IllegalStateException newException(String message)
557     {
558         IllegalStateException exception = new IllegalStateException(message);
559         logger.warn("Message access violation", exception);
560         return exception;
561     }
562 
563     protected boolean isDisabled()
564     {
565         return org.apache.commons.collections.MapUtils.getBooleanValue(System.getProperties(),
566                 MuleProperties.MULE_THREAD_UNSAFE_MESSAGES_PROPERTY, !DEFAULT_FAILFAST);
567     }
568 
569     private synchronized void initAccessControl()
570     {
571         if (null == ownerThread)
572         {
573             ownerThread = new AtomicReference();
574         }
575         if (null == mutable)
576         {
577             mutable = new AtomicBoolean(true);
578         }
579     }
580 
581     public synchronized void resetAccessControl()
582     {
583         assertAccess(WRITE);
584         ownerThread.set(null);
585         mutable.set(true);
586     }
587 
588     /**
589      * By default we return "this".  This allows older code to inter-operate but doesn't,
590      * of course, give the required safety.  Subclasses should override this method.
591      * Re-writing the threading handling should remove this requirement....
592      *
593      * @return A new copy of this
594      */
595     public ThreadSafeAccess newThreadCopy()
596     {
597         if (logger.isInfoEnabled())
598         {
599             logger.info("The newThreadCopy method in AbstractMessageAdapter is being used directly. "
600                     + "This code may be susceptible to 'scribbling' issues with messages. "
601                     + "Please consider implementing the ThreadSafeAccess interface in the message adapter.");
602         }
603         return this;
604     }
605 
606 }