View Javadoc

1   /************************************************************************
2    * Copyright (c) 2000-2006 The Apache Software Foundation.             *
3    * All rights reserved.                                                *
4    * ------------------------------------------------------------------- *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you *
6    * may not use this file except in compliance with the License. You    *
7    * may obtain a copy of the License at:                                *
8    *                                                                     *
9    *     http://www.apache.org/licenses/LICENSE-2.0                      *
10   *                                                                     *
11   * Unless required by applicable law or agreed to in writing, software *
12   * distributed under the License is distributed on an "AS IS" BASIS,   *
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or     *
14   * implied.  See the License for the specific language governing       *
15   * permissions and limitations under the License.                      *
16   ***********************************************************************/
17  
18  package org.apache.james.core;
19  
20  import org.apache.avalon.framework.activity.Disposable;
21  import org.apache.avalon.framework.container.ContainerUtil;
22  import org.apache.mailet.Mail;
23  import org.apache.mailet.MailAddress;
24  import org.apache.mailet.RFC2822Headers;
25  
26  import javax.mail.Address;
27  import javax.mail.MessagingException;
28  import javax.mail.internet.InternetAddress;
29  import javax.mail.internet.MimeMessage;
30  import javax.mail.internet.ParseException;
31  
32  import java.io.ByteArrayInputStream;
33  import java.io.ByteArrayOutputStream;
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.io.ObjectInputStream;
37  import java.io.ObjectOutputStream;
38  import java.io.OptionalDataException;
39  import java.io.OutputStream;
40  import java.io.Serializable;
41  import java.util.ArrayList;
42  import java.util.Collection;
43  import java.util.Date;
44  import java.util.HashMap;
45  import java.util.Iterator;
46  
47  /***
48   * <P>Wraps a MimeMessage adding routing information (from SMTP) and some simple
49   * API enhancements.</P>
50   * <P>From James version > 2.2.0a8 "mail attributes" have been added.
51   * Backward and forward compatibility is supported:
52   * messages stored in file repositories <I>without</I> attributes by James version <= 2.2.0a8
53   * will be processed by later versions as having an empty attributes hashmap;
54   * messages stored in file repositories <I>with</I> attributes by James version > 2.2.0a8
55   * will be processed by previous versions, ignoring the attributes.</P>
56   *
57   * @version CVS $Revision: 442571 $ $Date: 2006-09-12 12:38:57 +0000 (mar, 12 set 2006) $
58   */
59  public class MailImpl implements Disposable, Mail {
60      
61      /***
62       * We hardcode the serialVersionUID so that from James 1.2 on,
63       * MailImpl will be deserializable (so your mail doesn't get lost)
64       */
65      public static final long serialVersionUID = -4289663364703986260L;
66      /***
67       * The error message, if any, associated with this mail.
68       */
69      private String errorMessage;
70      /***
71       * The state of this mail, which determines how it is processed.
72       */
73      private String state;
74      /***
75       * The MimeMessage that holds the mail data.
76       */
77      private MimeMessage message;
78      /***
79       * The sender of this mail.
80       */
81      private MailAddress sender;
82      /***
83       * The collection of recipients to whom this mail was sent.
84       */
85      private Collection recipients;
86      /***
87       * The identifier for this mail message
88       */
89      private String name;
90      /***
91       * The remote host from which this mail was sent.
92       */
93      private String remoteHost = "localhost";
94      /***
95       * The remote address from which this mail was sent.
96       */
97      private String remoteAddr = "127.0.0.1";
98      /***
99       * The last time this message was updated.
100      */
101     private Date lastUpdated = new Date();
102     /***
103      * Attributes added to this MailImpl instance
104      */
105     private HashMap attributes;
106     /***
107      * A constructor that creates a new, uninitialized MailImpl
108      */
109     public MailImpl() {
110         setState(Mail.DEFAULT);
111         attributes = new HashMap();
112     }
113     /***
114      * A constructor that creates a MailImpl with the specified name,
115      * sender, and recipients.
116      *
117      * @param name the name of the MailImpl
118      * @param sender the sender for this MailImpl
119      * @param recipients the collection of recipients of this MailImpl
120      */
121     public MailImpl(String name, MailAddress sender, Collection recipients) {
122         this();
123         this.name = name;
124         this.sender = sender;
125         this.recipients = null;
126 
127         // Copy the recipient list
128         if (recipients != null) {
129             Iterator theIterator = recipients.iterator();
130             this.recipients = new ArrayList();
131             while (theIterator.hasNext()) {
132                 this.recipients.add(theIterator.next());
133             }
134         }
135     }
136 
137     /***
138      * @param mail
139      * @param newName
140      * @throws MessagingException
141      */
142     public MailImpl(Mail mail, String newName) throws MessagingException {
143         this(newName, mail.getSender(), mail.getRecipients(), mail.getMessage());
144         setRemoteHost(mail.getRemoteHost());
145         setRemoteAddr(mail.getRemoteAddr());
146         setLastUpdated(mail.getLastUpdated());
147         try {
148             if (mail instanceof MailImpl) {
149                 setAttributesRaw((HashMap) cloneSerializableObject(((MailImpl) mail).getAttributesRaw()));
150             } else {
151                 HashMap attribs = new HashMap();
152                 for (Iterator i = mail.getAttributeNames(); i.hasNext(); ) {
153                     String hashKey = (String) i.next();
154                     attribs.put(hashKey,cloneSerializableObject(mail.getAttribute(hashKey)));
155                 }
156                 setAttributesRaw(attribs);
157             }
158         } catch (IOException e) {
159             // should never happen for in memory streams
160             setAttributesRaw(new HashMap());
161         } catch (ClassNotFoundException e) {
162             // should never happen as we just serialized it
163             setAttributesRaw(new HashMap());
164         }
165     }
166 
167     /***
168      * A constructor that creates a MailImpl with the specified name,
169      * sender, recipients, and message data.
170      *
171      * @param name the name of the MailImpl
172      * @param sender the sender for this MailImpl
173      * @param recipients the collection of recipients of this MailImpl
174      * @param messageIn a stream containing the message source
175      */
176     public MailImpl(String name, MailAddress sender, Collection recipients, InputStream messageIn)
177         throws MessagingException {
178         this(name, sender, recipients);
179         MimeMessageSource source = new MimeMessageInputStreamSource(name, messageIn);
180         this.setMessage(new MimeMessageCopyOnWriteProxy(source));
181     }
182 
183     /***
184      * A constructor that creates a MailImpl with the specified name,
185      * sender, recipients, and MimeMessage.
186      *
187      * @param name the name of the MailImpl
188      * @param sender the sender for this MailImpl
189      * @param recipients the collection of recipients of this MailImpl
190      * @param message the MimeMessage associated with this MailImpl
191      */
192     public MailImpl(String name, MailAddress sender, Collection recipients, MimeMessage message) throws MessagingException {
193         this(name, sender, recipients);
194         this.setMessage(new MimeMessageCopyOnWriteProxy(message));
195     }
196 
197     /***
198      * A constructor which will attempt to obtain sender and recipients from the headers of the MimeMessage supplied.
199      * @param message - a MimeMessage from which to construct a Mail
200      */
201     public MailImpl(MimeMessage message) throws MessagingException {
202         this();
203         MailAddress sender = getReturnPath(message);
204         Collection recipients = null;
205         Address[] addresses = message.getRecipients(MimeMessage.RecipientType.TO);
206         if (addresses != null) {
207             recipients = new ArrayList();
208             for (int i = 0; i < addresses.length; i++) {
209                 try {
210                     recipients.add(new MailAddress(new InternetAddress(addresses[i].toString(), false)));
211                 } catch (ParseException pe) {
212                     // RFC 2822 section 3.4 allows To: fields without <>
213                     // Let's give this one more try with <>.
214                     try {
215                         recipients.add(new MailAddress("<" + new InternetAddress(addresses[i].toString()).toString() + ">"));
216                     } catch (ParseException _) {
217                         throw new MessagingException("Could not parse address: " + addresses[i].toString() + " from " + message.getHeader(RFC2822Headers.TO, ", "), pe);
218                     }
219                 }
220             }
221         }
222         this.name = message.toString();
223         this.sender = sender;
224         this.recipients = recipients;
225         this.setMessage(message);
226     }
227     /***
228      * Gets the MailAddress corresponding to the existing "Return-Path" of
229      * <I>message</I>.
230      * If missing or empty returns <CODE>null</CODE>,
231      */
232     private MailAddress getReturnPath(MimeMessage message) throws MessagingException {
233         MailAddress mailAddress = null;
234         String[] returnPathHeaders = message.getHeader(RFC2822Headers.RETURN_PATH);
235         String returnPathHeader = null;
236         if (returnPathHeaders != null) {
237             returnPathHeader = returnPathHeaders[0];
238             if (returnPathHeader != null) {
239                 returnPathHeader = returnPathHeader.trim();
240                 if (!returnPathHeader.equals("<>")) {
241                     try {
242                         mailAddress = new MailAddress(new InternetAddress(returnPathHeader, false));
243                     } catch (ParseException pe) {
244                         throw new MessagingException("Could not parse address: " + returnPathHeader + " from " + message.getHeader(RFC2822Headers.RETURN_PATH, ", "), pe);
245                     }
246                 }
247             }
248         }
249         return mailAddress;
250     }
251     /***
252      * Duplicate the MailImpl.
253      *
254      * @return a MailImpl that is a duplicate of this one
255      */
256     public Mail duplicate() {
257         return duplicate(name);
258     }
259     /***
260      * Duplicate the MailImpl, replacing the mail name with the one
261      * passed in as an argument.
262      *
263      * @param newName the name for the duplicated mail
264      *
265      * @return a MailImpl that is a duplicate of this one with a different name
266      */
267     public Mail duplicate(String newName) {
268         try {
269             return new MailImpl(this, newName);
270         } catch (MessagingException me) {
271             // Ignored.  Return null in the case of an error.
272         }
273         return null;
274     }
275     /***
276      * Get the error message associated with this MailImpl.
277      *
278      * @return the error message associated with this MailImpl
279      */
280     public String getErrorMessage() {
281         return errorMessage;
282     }
283     /***
284      * Get the MimeMessage associated with this MailImpl.
285      *
286      * @return the MimeMessage associated with this MailImpl
287      */
288     public MimeMessage getMessage() throws MessagingException {
289         return message;
290     }
291     
292     /***
293      * Set the name of this MailImpl.
294      *
295      * @param name the name of this MailImpl
296      */
297     public void setName(String name) {
298         this.name = name;
299     }
300     /***
301      * Get the name of this MailImpl.
302      *
303      * @return the name of this MailImpl
304      */
305     public String getName() {
306         return name;
307     }
308     /***
309      * Get the recipients of this MailImpl.
310      *
311      * @return the recipients of this MailImpl
312      */
313     public Collection getRecipients() {
314         return recipients;
315     }
316     /***
317      * Get the sender of this MailImpl.
318      *
319      * @return the sender of this MailImpl
320      */
321     public MailAddress getSender() {
322         return sender;
323     }
324     /***
325      * Get the state of this MailImpl.
326      *
327      * @return the state of this MailImpl
328      */
329     public String getState() {
330         return state;
331     }
332     /***
333      * Get the remote host associated with this MailImpl.
334      *
335      * @return the remote host associated with this MailImpl
336      */
337     public String getRemoteHost() {
338         return remoteHost;
339     }
340     /***
341      * Get the remote address associated with this MailImpl.
342      *
343      * @return the remote address associated with this MailImpl
344      */
345     public String getRemoteAddr() {
346         return remoteAddr;
347     }
348     /***
349      * Get the last updated time for this MailImpl.
350      *
351      * @return the last updated time for this MailImpl
352      */
353     public Date getLastUpdated() {
354         return lastUpdated;
355     }
356     
357     /***
358      * <p>Return the size of the message including its headers.
359      * MimeMessage.getSize() method only returns the size of the
360      * message body.</p>
361      *
362      * <p>Note: this size is not guaranteed to be accurate - see Sun's
363      * documentation of MimeMessage.getSize().</p>
364      *
365      * @return approximate size of full message including headers.
366      *
367      * @throws MessagingException if a problem occurs while computing the message size
368      */
369     public long getMessageSize() throws MessagingException {
370         return MimeMessageUtil.getMessageSize(message);
371     }
372     
373     /***
374      * Set the error message associated with this MailImpl.
375      *
376      * @param msg the new error message associated with this MailImpl
377      */
378     public void setErrorMessage(String msg) {
379         this.errorMessage = msg;
380     }
381     /***
382      * Set the MimeMessage associated with this MailImpl.
383      *
384      * @param message the new MimeMessage associated with this MailImpl
385      */
386     public void setMessage(MimeMessage message) {
387         if (this.message != message) {
388             // If a setMessage is called on a Mail that already have a message
389             // (discouraged) we have to make sure that the message we remove is
390             // correctly unreferenced and disposed, otherwise it will keep locks
391             if (this.message != null) {
392                 ContainerUtil.dispose(this.message);
393             }
394             this.message = message;
395         }
396     }
397     /***
398      * Set the recipients for this MailImpl.
399      *
400      * @param recipients the recipients for this MailImpl
401      */
402     public void setRecipients(Collection recipients) {
403         this.recipients = recipients;
404     }
405     /***
406      * Set the sender of this MailImpl.
407      *
408      * @param sender the sender of this MailImpl
409      */
410     public void setSender(MailAddress sender) {
411         this.sender = sender;
412     }
413     /***
414      * Set the state of this MailImpl.
415      *
416      * @param state the state of this MailImpl
417      */
418     public void setState(String state) {
419         this.state = state;
420     }
421     /***
422      * Set the remote address associated with this MailImpl.
423      *
424      * @param remoteHost the new remote host associated with this MailImpl
425      */
426     public void setRemoteHost(String remoteHost) {
427         this.remoteHost = remoteHost;
428     }
429     /***
430      * Set the remote address associated with this MailImpl.
431      *
432      * @param remoteAddr the new remote address associated with this MailImpl
433      */
434     public void setRemoteAddr(String remoteAddr) {
435         this.remoteAddr = remoteAddr;
436     }
437     /***
438      * Set the date this mail was last updated.
439      *
440      * @param lastUpdated the date the mail was last updated
441      */
442     public void setLastUpdated(Date lastUpdated) {
443         // Make a defensive copy to ensure that the date
444         // doesn't get changed external to the class
445         if (lastUpdated != null) {
446             lastUpdated = new Date(lastUpdated.getTime());
447         }
448         this.lastUpdated = lastUpdated;
449     }
450     /***
451      * Writes the message out to an OutputStream.
452      *
453      * @param out the OutputStream to which to write the content
454      *
455      * @throws MessagingException if the MimeMessage is not set for this MailImpl
456      * @throws IOException if an error occurs while reading or writing from the stream
457      */
458     public void writeMessageTo(OutputStream out) throws IOException, MessagingException {
459         if (message != null) {
460             message.writeTo(out);
461         } else {
462             throw new MessagingException("No message set for this MailImpl.");
463         }
464     }
465     
466     // Serializable Methods
467     // TODO: These need some work.  Currently very tightly coupled to
468     //       the internal representation.
469     /***
470      * Read the MailImpl from an <code>ObjectInputStream</code>.
471      *
472      * @param in the ObjectInputStream from which the object is read
473      *
474      * @throws IOException if an error occurs while reading from the stream
475      * @throws ClassNotFoundException ?
476      * @throws ClassCastException if the serialized objects are not of the appropriate type
477      */
478     private void readObject(java.io.ObjectInputStream in)
479         throws IOException, ClassNotFoundException {
480         try {
481             Object obj = in.readObject();
482             if (obj == null) {
483                 sender = null;
484             } else if (obj instanceof String) {
485                 sender = new MailAddress((String) obj);
486             } else if (obj instanceof MailAddress) {
487                 sender = (MailAddress) obj;
488             }
489         } catch (ParseException pe) {
490             throw new IOException("Error parsing sender address: " + pe.getMessage());
491         }
492         recipients = (Collection) in.readObject();
493         state = (String) in.readObject();
494         errorMessage = (String) in.readObject();
495         name = (String) in.readObject();
496         remoteHost = (String) in.readObject();
497         remoteAddr = (String) in.readObject();
498         setLastUpdated((Date) in.readObject());
499         // the following is under try/catch to be backwards compatible
500         // with messages created with James version <= 2.2.0a8
501         try {
502             attributes = (HashMap) in.readObject();
503         } catch (OptionalDataException ode) {
504             if (ode.eof) {
505                 attributes = new HashMap();
506             } else {
507                 throw ode;
508             }
509         }
510     }
511     /***
512      * Write the MailImpl to an <code>ObjectOutputStream</code>.
513      *
514      * @param in the ObjectOutputStream to which the object is written
515      *
516      * @throws IOException if an error occurs while writing to the stream
517      */
518     private void writeObject(java.io.ObjectOutputStream out) throws IOException {
519         out.writeObject(sender);
520         out.writeObject(recipients);
521         out.writeObject(state);
522         out.writeObject(errorMessage);
523         out.writeObject(name);
524         out.writeObject(remoteHost);
525         out.writeObject(remoteAddr);
526         out.writeObject(lastUpdated);
527         out.writeObject(attributes);
528     }
529 
530     /***
531      * @see org.apache.avalon.framework.activity.Disposable#dispose()
532      */
533     public void dispose() {
534         ContainerUtil.dispose(message);
535         message = null;
536     }
537 
538     /***
539      * This method is necessary, when Mail repositories needs to deal
540      * explicitly with storing Mail attributes as a Serializable
541      * Note: This method is not exposed in the Mail interface,
542      * it is for internal use by James only.
543      * @return Serializable of the entire attributes collection
544      * @since 2.2.0
545      **/
546     public HashMap getAttributesRaw () {
547         return attributes;
548     }
549     
550     /***
551      * This method is necessary, when Mail repositories needs to deal
552      * explicitly with retriving Mail attributes as a Serializable
553      * Note: This method is not exposed in the Mail interface,
554      * it is for internal use by James only.
555      * @return Serializable of the entire attributes collection
556      * @since 2.2.0
557      **/
558     public void setAttributesRaw (HashMap attr)
559     {
560         this.attributes = (attr == null) ? new HashMap() : attr;
561     }
562 
563     /***
564      * @see org.apache.mailet.Mail#getAttribute(String)
565      * @since 2.2.0
566      */
567     public Serializable getAttribute(String key) {
568         return (Serializable)attributes.get(key);
569     }
570     /***
571      * @see org.apache.mailet.Mail#setAttribute(String,Serializable)
572      * @since 2.2.0
573      */
574     public Serializable setAttribute(String key, Serializable object) {
575         return (Serializable)attributes.put(key, object);
576     }
577     /***
578      * @see org.apache.mailet.Mail#removeAttribute(String)
579      * @since 2.2.0
580      */
581     public Serializable removeAttribute(String key) {
582         return (Serializable)attributes.remove(key);
583     }
584     /***
585      * @see org.apache.mailet.Mail#removeAllAttributes()
586      * @since 2.2.0
587      */
588     public void removeAllAttributes() {
589         attributes.clear();
590     }
591     /***
592      * @see org.apache.mailet.Mail#getAttributeNames()
593      * @since 2.2.0
594      */
595     public Iterator getAttributeNames() {
596         return attributes.keySet().iterator();
597     }
598     /***
599      * @see org.apache.mailet.Mail#hasAttributes()
600      * @since 2.2.0
601      */
602     public boolean hasAttributes() {
603         return !attributes.isEmpty();
604     }
605 
606 
607     /***
608      * This methods provide cloning for serializable objects.
609      * Mail Attributes are Serializable but not Clonable so we need a deep copy
610      *
611      * @param input Object to be cloned
612      * @return the cloned Object
613      * @throws IOException
614      * @throws ClassNotFoundException
615      */
616     private static Object cloneSerializableObject(Object o) throws IOException, ClassNotFoundException {
617         ByteArrayOutputStream b = new ByteArrayOutputStream();
618         ObjectOutputStream out = new ObjectOutputStream(b);
619         out.writeObject(o);
620         out.flush();
621         out.close();
622         ByteArrayInputStream bi=new ByteArrayInputStream(b.toByteArray());
623         ObjectInputStream in = new ObjectInputStream(bi);
624         Object no = in.readObject();
625         return no;
626     }
627 }