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
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
160 setAttributesRaw(new HashMap());
161 } catch (ClassNotFoundException e) {
162
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
213
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
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
389
390
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
444
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
467
468
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
500
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 }