001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.core;
019:
020: import org.apache.avalon.framework.activity.Disposable;
021: import org.apache.avalon.framework.container.ContainerUtil;
022: import org.apache.mailet.Mail;
023: import org.apache.mailet.MailAddress;
024: import org.apache.mailet.RFC2822Headers;
025:
026: import javax.mail.Address;
027: import javax.mail.MessagingException;
028: import javax.mail.internet.InternetAddress;
029: import javax.mail.internet.MimeMessage;
030: import javax.mail.internet.ParseException;
031:
032: import java.io.ByteArrayInputStream;
033: import java.io.ByteArrayOutputStream;
034: import java.io.IOException;
035: import java.io.InputStream;
036: import java.io.ObjectInputStream;
037: import java.io.ObjectOutputStream;
038: import java.io.OptionalDataException;
039: import java.io.OutputStream;
040: import java.io.Serializable;
041: import java.util.ArrayList;
042: import java.util.Collection;
043: import java.util.Date;
044: import java.util.HashMap;
045: import java.util.Iterator;
046:
047: /**
048: * <P>Wraps a MimeMessage adding routing information (from SMTP) and some simple
049: * API enhancements.</P>
050: * <P>From James version > 2.2.0a8 "mail attributes" have been added.
051: * Backward and forward compatibility is supported:
052: * messages stored in file repositories <I>without</I> attributes by James version <= 2.2.0a8
053: * will be processed by later versions as having an empty attributes hashmap;
054: * messages stored in file repositories <I>with</I> attributes by James version > 2.2.0a8
055: * will be processed by previous versions, ignoring the attributes.</P>
056: *
057: * @version CVS $Revision: 494012 $ $Date: 2007-01-08 11:23:58 +0100 (Mo, 08 Jan 2007) $
058: */
059: public class MailImpl implements Disposable, Mail {
060:
061: /**
062: * We hardcode the serialVersionUID so that from James 1.2 on,
063: * MailImpl will be deserializable (so your mail doesn't get lost)
064: */
065: public static final long serialVersionUID = -4289663364703986260L;
066: /**
067: * The error message, if any, associated with this mail.
068: */
069: private String errorMessage;
070: /**
071: * The state of this mail, which determines how it is processed.
072: */
073: private String state;
074: /**
075: * The MimeMessage that holds the mail data.
076: */
077: private MimeMessage message;
078: /**
079: * The sender of this mail.
080: */
081: private MailAddress sender;
082: /**
083: * The collection of recipients to whom this mail was sent.
084: */
085: private Collection recipients;
086: /**
087: * The identifier for this mail message
088: */
089: private String name;
090: /**
091: * The remote host from which this mail was sent.
092: */
093: private String remoteHost = "localhost";
094: /**
095: * The remote address from which this mail was sent.
096: */
097: private String remoteAddr = "127.0.0.1";
098: /**
099: * 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: /**
108: * A constructor that creates a new, uninitialized MailImpl
109: */
110: public MailImpl() {
111: setState(Mail.DEFAULT);
112: attributes = new HashMap();
113: }
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,
124: Collection recipients) {
125: this ();
126: this .name = name;
127: this .sender = sender;
128: this .recipients = null;
129:
130: // Copy the recipient list
131: if (recipients != null) {
132: Iterator theIterator = recipients.iterator();
133: this .recipients = new ArrayList();
134: while (theIterator.hasNext()) {
135: this .recipients.add(theIterator.next());
136: }
137: }
138: }
139:
140: /**
141: * @param mail
142: * @param newName
143: * @throws MessagingException
144: */
145: public MailImpl(Mail mail, String newName)
146: throws MessagingException {
147: this (newName, mail.getSender(), mail.getRecipients(), mail
148: .getMessage());
149: setRemoteHost(mail.getRemoteHost());
150: setRemoteAddr(mail.getRemoteAddr());
151: setLastUpdated(mail.getLastUpdated());
152: try {
153: if (mail instanceof MailImpl) {
154: setAttributesRaw((HashMap) cloneSerializableObject(((MailImpl) mail)
155: .getAttributesRaw()));
156: } else {
157: HashMap attribs = new HashMap();
158: for (Iterator i = mail.getAttributeNames(); i.hasNext();) {
159: String hashKey = (String) i.next();
160: attribs.put(hashKey, cloneSerializableObject(mail
161: .getAttribute(hashKey)));
162: }
163: setAttributesRaw(attribs);
164: }
165: } catch (IOException e) {
166: // should never happen for in memory streams
167: setAttributesRaw(new HashMap());
168: } catch (ClassNotFoundException e) {
169: // should never happen as we just serialized it
170: setAttributesRaw(new HashMap());
171: }
172: }
173:
174: /**
175: * A constructor that creates a MailImpl with the specified name,
176: * sender, recipients, and message data.
177: *
178: * @param name the name of the MailImpl
179: * @param sender the sender for this MailImpl
180: * @param recipients the collection of recipients of this MailImpl
181: * @param messageIn a stream containing the message source
182: */
183: public MailImpl(String name, MailAddress sender,
184: Collection recipients, InputStream messageIn)
185: throws MessagingException {
186: this (name, sender, recipients);
187: MimeMessageSource source = new MimeMessageInputStreamSource(
188: name, messageIn);
189: this .setMessage(new MimeMessageCopyOnWriteProxy(source));
190: }
191:
192: /**
193: * A constructor that creates a MailImpl with the specified name,
194: * sender, recipients, and MimeMessage.
195: *
196: * @param name the name of the MailImpl
197: * @param sender the sender for this MailImpl
198: * @param recipients the collection of recipients of this MailImpl
199: * @param message the MimeMessage associated with this MailImpl
200: */
201: public MailImpl(String name, MailAddress sender,
202: Collection recipients, MimeMessage message)
203: throws MessagingException {
204: this (name, sender, recipients);
205: this .setMessage(new MimeMessageCopyOnWriteProxy(message));
206: }
207:
208: /**
209: * A constructor which will attempt to obtain sender and recipients from the headers of the MimeMessage supplied.
210: * @param message - a MimeMessage from which to construct a Mail
211: */
212: public MailImpl(MimeMessage message) throws MessagingException {
213: this ();
214: MailAddress sender = getReturnPath(message);
215: Collection recipients = null;
216: Address[] addresses = message
217: .getRecipients(MimeMessage.RecipientType.TO);
218: if (addresses != null) {
219: recipients = new ArrayList();
220: for (int i = 0; i < addresses.length; i++) {
221: try {
222: recipients.add(new MailAddress(new InternetAddress(
223: addresses[i].toString(), false)));
224: } catch (ParseException pe) {
225: // RFC 2822 section 3.4 allows To: fields without <>
226: // Let's give this one more try with <>.
227: try {
228: recipients.add(new MailAddress("<"
229: + new InternetAddress(addresses[i]
230: .toString()).toString() + ">"));
231: } catch (ParseException _) {
232: throw new MessagingException(
233: "Could not parse address: "
234: + addresses[i].toString()
235: + " from "
236: + message
237: .getHeader(
238: RFC2822Headers.TO,
239: ", "), pe);
240: }
241: }
242: }
243: }
244: this .name = message.toString();
245: this .sender = sender;
246: this .recipients = recipients;
247: this .setMessage(message);
248: }
249:
250: /**
251: * Gets the MailAddress corresponding to the existing "Return-Path" of
252: * <I>message</I>.
253: * If missing or empty returns <CODE>null</CODE>,
254: */
255: private MailAddress getReturnPath(MimeMessage message)
256: throws MessagingException {
257: MailAddress mailAddress = null;
258: String[] returnPathHeaders = message
259: .getHeader(RFC2822Headers.RETURN_PATH);
260: String returnPathHeader = null;
261: if (returnPathHeaders != null) {
262: returnPathHeader = returnPathHeaders[0];
263: if (returnPathHeader != null) {
264: returnPathHeader = returnPathHeader.trim();
265: if (!returnPathHeader.equals("<>")) {
266: try {
267: mailAddress = new MailAddress(
268: new InternetAddress(returnPathHeader,
269: false));
270: } catch (ParseException pe) {
271: throw new MessagingException(
272: "Could not parse address: "
273: + returnPathHeader
274: + " from "
275: + message
276: .getHeader(
277: RFC2822Headers.RETURN_PATH,
278: ", "), pe);
279: }
280: }
281: }
282: }
283: return mailAddress;
284: }
285:
286: /**
287: * Duplicate the MailImpl.
288: *
289: * @return a MailImpl that is a duplicate of this one
290: */
291: public Mail duplicate() {
292: return duplicate(name);
293: }
294:
295: /**
296: * Duplicate the MailImpl, replacing the mail name with the one
297: * passed in as an argument.
298: *
299: * @param newName the name for the duplicated mail
300: *
301: * @return a MailImpl that is a duplicate of this one with a different name
302: */
303: public Mail duplicate(String newName) {
304: try {
305: return new MailImpl(this , newName);
306: } catch (MessagingException me) {
307: // Ignored. Return null in the case of an error.
308: }
309: return null;
310: }
311:
312: /**
313: * Get the error message associated with this MailImpl.
314: *
315: * @return the error message associated with this MailImpl
316: */
317: public String getErrorMessage() {
318: return errorMessage;
319: }
320:
321: /**
322: * Get the MimeMessage associated with this MailImpl.
323: *
324: * @return the MimeMessage associated with this MailImpl
325: */
326: public MimeMessage getMessage() throws MessagingException {
327: return message;
328: }
329:
330: /**
331: * Set the name of this MailImpl.
332: *
333: * @param name the name of this MailImpl
334: */
335: public void setName(String name) {
336: this .name = name;
337: }
338:
339: /**
340: * Get the name of this MailImpl.
341: *
342: * @return the name of this MailImpl
343: */
344: public String getName() {
345: return name;
346: }
347:
348: /**
349: * Get the recipients of this MailImpl.
350: *
351: * @return the recipients of this MailImpl
352: */
353: public Collection getRecipients() {
354: return recipients;
355: }
356:
357: /**
358: * Get the sender of this MailImpl.
359: *
360: * @return the sender of this MailImpl
361: */
362: public MailAddress getSender() {
363: return sender;
364: }
365:
366: /**
367: * Get the state of this MailImpl.
368: *
369: * @return the state of this MailImpl
370: */
371: public String getState() {
372: return state;
373: }
374:
375: /**
376: * Get the remote host associated with this MailImpl.
377: *
378: * @return the remote host associated with this MailImpl
379: */
380: public String getRemoteHost() {
381: return remoteHost;
382: }
383:
384: /**
385: * Get the remote address associated with this MailImpl.
386: *
387: * @return the remote address associated with this MailImpl
388: */
389: public String getRemoteAddr() {
390: return remoteAddr;
391: }
392:
393: /**
394: * Get the last updated time for this MailImpl.
395: *
396: * @return the last updated time for this MailImpl
397: */
398: public Date getLastUpdated() {
399: return lastUpdated;
400: }
401:
402: /**
403: * <p>Return the size of the message including its headers.
404: * MimeMessage.getSize() method only returns the size of the
405: * message body.</p>
406: *
407: * <p>Note: this size is not guaranteed to be accurate - see Sun's
408: * documentation of MimeMessage.getSize().</p>
409: *
410: * @return approximate size of full message including headers.
411: *
412: * @throws MessagingException if a problem occurs while computing the message size
413: */
414: public long getMessageSize() throws MessagingException {
415: return MimeMessageUtil.getMessageSize(message);
416: }
417:
418: /**
419: * Set the error message associated with this MailImpl.
420: *
421: * @param msg the new error message associated with this MailImpl
422: */
423: public void setErrorMessage(String msg) {
424: this .errorMessage = msg;
425: }
426:
427: /**
428: * Set the MimeMessage associated with this MailImpl.
429: *
430: * @param message the new MimeMessage associated with this MailImpl
431: */
432: public void setMessage(MimeMessage message) {
433: if (this .message != message) {
434: // If a setMessage is called on a Mail that already have a message
435: // (discouraged) we have to make sure that the message we remove is
436: // correctly unreferenced and disposed, otherwise it will keep locks
437: if (this .message != null) {
438: ContainerUtil.dispose(this .message);
439: }
440: this .message = message;
441: }
442: }
443:
444: /**
445: * Set the recipients for this MailImpl.
446: *
447: * @param recipients the recipients for this MailImpl
448: */
449: public void setRecipients(Collection recipients) {
450: this .recipients = recipients;
451: }
452:
453: /**
454: * Set the sender of this MailImpl.
455: *
456: * @param sender the sender of this MailImpl
457: */
458: public void setSender(MailAddress sender) {
459: this .sender = sender;
460: }
461:
462: /**
463: * Set the state of this MailImpl.
464: *
465: * @param state the state of this MailImpl
466: */
467: public void setState(String state) {
468: this .state = state;
469: }
470:
471: /**
472: * Set the remote address associated with this MailImpl.
473: *
474: * @param remoteHost the new remote host associated with this MailImpl
475: */
476: public void setRemoteHost(String remoteHost) {
477: this .remoteHost = remoteHost;
478: }
479:
480: /**
481: * Set the remote address associated with this MailImpl.
482: *
483: * @param remoteAddr the new remote address associated with this MailImpl
484: */
485: public void setRemoteAddr(String remoteAddr) {
486: this .remoteAddr = remoteAddr;
487: }
488:
489: /**
490: * Set the date this mail was last updated.
491: *
492: * @param lastUpdated the date the mail was last updated
493: */
494: public void setLastUpdated(Date lastUpdated) {
495: // Make a defensive copy to ensure that the date
496: // doesn't get changed external to the class
497: if (lastUpdated != null) {
498: lastUpdated = new Date(lastUpdated.getTime());
499: }
500: this .lastUpdated = lastUpdated;
501: }
502:
503: /**
504: * Writes the message out to an OutputStream.
505: *
506: * @param out the OutputStream to which to write the content
507: *
508: * @throws MessagingException if the MimeMessage is not set for this MailImpl
509: * @throws IOException if an error occurs while reading or writing from the stream
510: */
511: public void writeMessageTo(OutputStream out) throws IOException,
512: MessagingException {
513: if (message != null) {
514: message.writeTo(out);
515: } else {
516: throw new MessagingException(
517: "No message set for this MailImpl.");
518: }
519: }
520:
521: // Serializable Methods
522: // TODO: These need some work. Currently very tightly coupled to
523: // the internal representation.
524: /**
525: * Read the MailImpl from an <code>ObjectInputStream</code>.
526: *
527: * @param in the ObjectInputStream from which the object is read
528: *
529: * @throws IOException if an error occurs while reading from the stream
530: * @throws ClassNotFoundException ?
531: * @throws ClassCastException if the serialized objects are not of the appropriate type
532: */
533: private void readObject(java.io.ObjectInputStream in)
534: throws IOException, ClassNotFoundException {
535: try {
536: Object obj = in.readObject();
537: if (obj == null) {
538: sender = null;
539: } else if (obj instanceof String) {
540: sender = new MailAddress((String) obj);
541: } else if (obj instanceof MailAddress) {
542: sender = (MailAddress) obj;
543: }
544: } catch (ParseException pe) {
545: throw new IOException("Error parsing sender address: "
546: + pe.getMessage());
547: }
548: recipients = (Collection) in.readObject();
549: state = (String) in.readObject();
550: errorMessage = (String) in.readObject();
551: name = (String) in.readObject();
552: remoteHost = (String) in.readObject();
553: remoteAddr = (String) in.readObject();
554: setLastUpdated((Date) in.readObject());
555: // the following is under try/catch to be backwards compatible
556: // with messages created with James version <= 2.2.0a8
557: try {
558: attributes = (HashMap) in.readObject();
559: } catch (OptionalDataException ode) {
560: if (ode.eof) {
561: attributes = new HashMap();
562: } else {
563: throw ode;
564: }
565: }
566: }
567:
568: /**
569: * Write the MailImpl to an <code>ObjectOutputStream</code>.
570: *
571: * @param in the ObjectOutputStream to which the object is written
572: *
573: * @throws IOException if an error occurs while writing to the stream
574: */
575: private void writeObject(java.io.ObjectOutputStream out)
576: throws IOException {
577: out.writeObject(sender);
578: out.writeObject(recipients);
579: out.writeObject(state);
580: out.writeObject(errorMessage);
581: out.writeObject(name);
582: out.writeObject(remoteHost);
583: out.writeObject(remoteAddr);
584: out.writeObject(lastUpdated);
585: out.writeObject(attributes);
586: }
587:
588: /**
589: * @see org.apache.avalon.framework.activity.Disposable#dispose()
590: */
591: public void dispose() {
592: ContainerUtil.dispose(message);
593: message = null;
594: }
595:
596: /**
597: * This method is necessary, when Mail repositories needs to deal
598: * explicitly with storing Mail attributes as a Serializable
599: * Note: This method is not exposed in the Mail interface,
600: * it is for internal use by James only.
601: * @return Serializable of the entire attributes collection
602: * @since 2.2.0
603: **/
604: public HashMap getAttributesRaw() {
605: return attributes;
606: }
607:
608: /**
609: * This method is necessary, when Mail repositories needs to deal
610: * explicitly with retriving Mail attributes as a Serializable
611: * Note: This method is not exposed in the Mail interface,
612: * it is for internal use by James only.
613: * @return Serializable of the entire attributes collection
614: * @since 2.2.0
615: **/
616: public void setAttributesRaw(HashMap attr) {
617: this .attributes = (attr == null) ? new HashMap() : attr;
618: }
619:
620: /**
621: * @see org.apache.mailet.Mail#getAttribute(String)
622: * @since 2.2.0
623: */
624: public Serializable getAttribute(String key) {
625: return (Serializable) attributes.get(key);
626: }
627:
628: /**
629: * @see org.apache.mailet.Mail#setAttribute(String,Serializable)
630: * @since 2.2.0
631: */
632: public Serializable setAttribute(String key, Serializable object) {
633: return (Serializable) attributes.put(key, object);
634: }
635:
636: /**
637: * @see org.apache.mailet.Mail#removeAttribute(String)
638: * @since 2.2.0
639: */
640: public Serializable removeAttribute(String key) {
641: return (Serializable) attributes.remove(key);
642: }
643:
644: /**
645: * @see org.apache.mailet.Mail#removeAllAttributes()
646: * @since 2.2.0
647: */
648: public void removeAllAttributes() {
649: attributes.clear();
650: }
651:
652: /**
653: * @see org.apache.mailet.Mail#getAttributeNames()
654: * @since 2.2.0
655: */
656: public Iterator getAttributeNames() {
657: return attributes.keySet().iterator();
658: }
659:
660: /**
661: * @see org.apache.mailet.Mail#hasAttributes()
662: * @since 2.2.0
663: */
664: public boolean hasAttributes() {
665: return !attributes.isEmpty();
666: }
667:
668: /**
669: * This methods provide cloning for serializable objects.
670: * Mail Attributes are Serializable but not Clonable so we need a deep copy
671: *
672: * @param input Object to be cloned
673: * @return the cloned Object
674: * @throws IOException
675: * @throws ClassNotFoundException
676: */
677: private static Object cloneSerializableObject(Object o)
678: throws IOException, ClassNotFoundException {
679: ByteArrayOutputStream b = new ByteArrayOutputStream();
680: ObjectOutputStream out = new ObjectOutputStream(b);
681: out.writeObject(o);
682: out.flush();
683: out.close();
684: ByteArrayInputStream bi = new ByteArrayInputStream(b
685: .toByteArray());
686: ObjectInputStream in = new ObjectInputStream(bi);
687: Object no = in.readObject();
688: return no;
689: }
690: }
|