View Javadoc

1   /*
2    * $Id: SimpleMailMessageAdapter.java 9773 2007-11-19 14:16:27Z 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.email;
12  
13  import org.mule.config.i18n.CoreMessages;
14  import org.mule.impl.ThreadSafeAccess;
15  import org.mule.providers.AbstractMessageAdapter;
16  import org.mule.umo.MessagingException;
17  import org.mule.umo.provider.MessageTypeNotSupportedException;
18  import org.mule.util.IOUtils;
19  import org.mule.util.StringUtils;
20  import org.mule.util.SystemUtils;
21  
22  import java.io.BufferedInputStream;
23  import java.io.BufferedReader;
24  import java.io.ByteArrayOutputStream;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.util.Date;
28  import java.util.Enumeration;
29  import java.util.LinkedList;
30  import java.util.List;
31  
32  import javax.mail.Header;
33  import javax.mail.Message;
34  import javax.mail.Part;
35  
36  /**
37   * <code>SimpleMailMessageAdapter</code> is an adapter for mail messages.
38   * Unlike {@link MailMessageAdapter} this preserves the message intact in its original
39   * form.
40   *
41   * <p>Header values are stored in two formats.  First, as historically used by
42   * {@link MailMessageAdapter}, a single String value is stored for each distinct
43   * header name (if a header is repeated only one value is stored).
44   * Secondly, a list of values for each distinct header is stored in a property name
45   * prefixed by HEADER_LIST_PREFIX
46   * (which produces an invalid header name according to RFC 822 and so (i) avoids
47   * conflict with the first property type and (ii) will cause current applications
48   * that wrongly assume all properties are simple header values to fail fast).
49   * The utility methods
50   * {@link #isListHeader(String)}, {@link #toHeader(String)} and
51   * {@link #toListHeader(String)} identify and convert between property and
52   * header names as required.
53   */
54  public class SimpleMailMessageAdapter extends AbstractMessageAdapter
55  {
56  
57      private static final long serialVersionUID = 8002607243523460556L;
58      public static final String HEADER_LIST_PREFIX = "List:";
59      private Part message;
60      private byte[] cache = null;
61  
62      public SimpleMailMessageAdapter(Object object) throws MessagingException
63      {
64          Message message = assertMessageType(object);
65  
66          try
67          {
68              setMessageDetails(message);
69              handleMessage(message);
70          }
71          catch (Exception e)
72          {
73              throw new MessagingException(CoreMessages.failedToCreate("Message Adapter"), e);
74          }
75      }
76  
77      protected SimpleMailMessageAdapter(SimpleMailMessageAdapter template)
78      {
79          super(template);
80          message = template.message;
81          cache = template.cache;
82      }
83  
84      /**
85       * By default, this simply stores the entire message as a single message.
86       * Sub-classes may override with more complex processing.
87       */
88      protected void handleMessage(Message message) throws Exception
89      {
90          setMessage(message);
91      }
92  
93      protected void setMessage(Part message)
94      {
95          this.message = message;
96      }
97  
98      public Object getPayload() {
99          return message;
100     }
101 
102     public byte[] getPayloadAsBytes() throws Exception
103     {
104         return buildCache(getEncoding());
105     }
106 
107     public String getPayloadAsString(String encoding) throws Exception
108     {
109         // TODO - i don't understand how encoding is used here
110         // could this method be called with various encodings?
111         // does that invalidate the cache?
112         // (ie there are two encodings -- one used to generate the cache from
113         // the mail message, and one used to generate the string from the cache)
114         return new String(buildCache(encoding), encoding);
115     }
116 
117     private byte[] buildCache(String encoding) throws Exception
118     {
119         if (null == cache)
120         {
121             if (message.getContentType().startsWith("text/"))
122             {
123                 cache = textPayload(encoding);
124             }
125             else
126             {
127                 cache = binaryPayload();
128             }
129         }
130         return cache;
131     }
132 
133     private static Message assertMessageType(Object message) throws MessageTypeNotSupportedException
134     {
135         if (message instanceof Message)
136         {
137             return (Message)message;
138         }
139         else
140         {
141             throw new MessageTypeNotSupportedException(message, MailMessageAdapter.class);
142         }
143     }
144 
145     private void setMessageDetails(Message message)  throws javax.mail.MessagingException
146     {
147         setProperty(MailProperties.INBOUND_TO_ADDRESSES_PROPERTY,
148             MailUtils.mailAddressesToString(message.getRecipients(Message.RecipientType.TO)));
149         setProperty(MailProperties.INBOUND_CC_ADDRESSES_PROPERTY,
150             MailUtils.mailAddressesToString(message.getRecipients(Message.RecipientType.CC)));
151         setProperty(MailProperties.INBOUND_BCC_ADDRESSES_PROPERTY,
152             MailUtils.mailAddressesToString(message.getRecipients(Message.RecipientType.BCC)));
153         try 
154         {
155 	        setProperty(MailProperties.INBOUND_REPLY_TO_ADDRESSES_PROPERTY,
156 	            MailUtils.mailAddressesToString(message.getReplyTo()));
157         } 
158         catch (javax.mail.MessagingException me) 
159         {
160         	logger.warn("Invalid address found in ReplyTo header:", me);
161         }
162         
163         try 
164         {
165 	        setProperty(MailProperties.INBOUND_FROM_ADDRESS_PROPERTY,
166 	            MailUtils.mailAddressesToString(message.getFrom()));
167 	    } 
168         catch (javax.mail.MessagingException me) 
169         {
170 	    	logger.warn("Invalid address found in From header:", me);
171 	    }
172         
173         setProperty(MailProperties.INBOUND_SUBJECT_PROPERTY, StringUtils.defaultIfEmpty(
174             message.getSubject(),"(no subject)"));
175         setProperty(MailProperties.INBOUND_CONTENT_TYPE_PROPERTY, StringUtils.defaultIfEmpty(
176             message.getContentType(), "text/plain"));
177 
178         Date sentDate = message.getSentDate();
179         if (sentDate == null)
180         {
181             sentDate = new Date();
182         }
183         setProperty(MailProperties.SENT_DATE_PROPERTY, sentDate);
184 
185         for (Enumeration e = message.getAllHeaders(); e.hasMoreElements();)
186         {
187             Header header = (Header)e.nextElement();
188             String name = header.getName();
189             String listName = toListHeader(name);
190             String value = header.getValue();
191 
192             if (null == getProperty(name))
193             {
194                 setProperty(name, value);
195             }
196 
197             if (null == getProperty(listName))
198             {
199                 setProperty(listName, new LinkedList());
200             }
201             if (getProperty(listName) instanceof List)
202             {
203                 ((List) getProperty(listName)).add(header.getValue());
204             }
205         }
206     }
207 
208     /**
209      * Check whether a property name has the format associated with a list
210      * of header values
211      * @param name A property name
212      * @return true if the name is associated with a list of header values
213      * (more exactly, if it starts with HEADER_LIST_PREFIX, which gives an
214      * invalid header name according to RFC822).
215      */
216     public static boolean isListHeader(String name)
217     {
218         return null != name && name.startsWith(HEADER_LIST_PREFIX);
219     }
220 
221     /**
222      * Convert a property name associated with a list of header values to
223      * the relevant header name (ie drop the prefix)
224      * @param name A property name
225      * @return The associated header name (ie with HEADER_LIST_PREFIX removed)
226      */
227     public static String toHeader(String name)
228     {
229         if (isListHeader(name))
230         {
231             return name.substring(HEADER_LIST_PREFIX.length());
232         }
233         else
234         {
235             return name;
236         }
237     }
238 
239     /**
240      * Convert a header name to the property name associated with a list of
241      * header values (ie prepend the prefix)
242      * @param header A header name
243      * @return The associated list property name (ie with HEADER_LIST_PREFIX prepended)
244      */
245     public static String toListHeader(String header)
246     {
247         if (isListHeader(header))
248         {
249             return header;
250         }
251         else
252         {
253             return HEADER_LIST_PREFIX + header;
254         }
255     }
256 
257     private static InputStream addBuffer(InputStream stream)
258     {
259         if (!(stream instanceof BufferedInputStream))
260         {
261             stream = new BufferedInputStream(stream);
262         }
263         return stream;
264     }
265 
266     private byte[] binaryPayload() throws Exception
267     {
268         InputStream stream = addBuffer(message.getInputStream());
269         ByteArrayOutputStream baos = new ByteArrayOutputStream(32768);
270         IOUtils.copy(stream, baos);
271         return baos.toByteArray();
272     }
273 
274     private byte[] textPayload(String encoding) throws Exception
275     {
276         InputStream stream = addBuffer(message.getInputStream());
277         BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
278         StringBuffer buffer = new StringBuffer(32768);
279 
280         String line;
281         while ((line = reader.readLine()) != null)
282         {
283             buffer.append(line).append(SystemUtils.LINE_SEPARATOR);
284         }
285 
286         return buffer.toString().getBytes(encoding);
287     }
288 
289     public ThreadSafeAccess newThreadCopy()
290     {
291         return new SimpleMailMessageAdapter(this);
292     }
293 }