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
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
162 setAttributesRaw(new HashMap());
163 } catch (ClassNotFoundException e) {
164
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
215
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
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
391
392
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
446
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
469
470
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
502
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 }