View Javadoc

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