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.mime4j.message;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.Date;
29 import java.util.TimeZone;
30
31 import org.apache.james.mime4j.MimeException;
32 import org.apache.james.mime4j.MimeIOException;
33 import org.apache.james.mime4j.field.AddressListField;
34 import org.apache.james.mime4j.field.DateTimeField;
35 import org.apache.james.mime4j.field.FieldName;
36 import org.apache.james.mime4j.field.Fields;
37 import org.apache.james.mime4j.field.MailboxField;
38 import org.apache.james.mime4j.field.MailboxListField;
39 import org.apache.james.mime4j.field.UnstructuredField;
40 import org.apache.james.mime4j.field.address.Address;
41 import org.apache.james.mime4j.field.address.AddressList;
42 import org.apache.james.mime4j.field.address.Mailbox;
43 import org.apache.james.mime4j.field.address.MailboxList;
44 import org.apache.james.mime4j.parser.Field;
45 import org.apache.james.mime4j.parser.MimeEntityConfig;
46 import org.apache.james.mime4j.parser.MimeStreamParser;
47 import org.apache.james.mime4j.storage.DefaultStorageProvider;
48 import org.apache.james.mime4j.storage.StorageProvider;
49
50 /**
51 * Represents a MIME message. The following code parses a stream into a
52 * <code>Message</code> object.
53 *
54 * <pre>
55 * Message msg = new Message(new FileInputStream("mime.msg"));
56 * </pre>
57 */
58 public class Message extends Entity implements Body {
59
60 /**
61 * Creates a new empty <code>Message</code>.
62 */
63 public Message() {
64 }
65
66 /**
67 * Creates a new <code>Message</code> from the specified
68 * <code>Message</code>. The <code>Message</code> instance is
69 * initialized with copies of header and body of the specified
70 * <code>Message</code>. The parent entity of the new message is
71 * <code>null</code>.
72 *
73 * @param other
74 * message to copy.
75 * @throws UnsupportedOperationException
76 * if <code>other</code> contains a {@link SingleBody} that
77 * does not support the {@link SingleBody#copy() copy()}
78 * operation.
79 * @throws IllegalArgumentException
80 * if <code>other</code> contains a <code>Body</code> that
81 * is neither a {@link Message}, {@link Multipart} or
82 * {@link SingleBody}.
83 */
84 public Message(Message other) {
85 super(other);
86 }
87
88 /**
89 * Parses the specified MIME message stream into a <code>Message</code>
90 * instance.
91 *
92 * @param is
93 * the stream to parse.
94 * @throws IOException
95 * on I/O errors.
96 * @throws MimeIOException
97 * on MIME protocol violations.
98 */
99 public Message(InputStream is) throws IOException, MimeIOException {
100 this(is, null, DefaultStorageProvider.getInstance());
101 }
102
103 /**
104 * Parses the specified MIME message stream into a <code>Message</code>
105 * instance using given {@link MimeEntityConfig}.
106 *
107 * @param is
108 * the stream to parse.
109 * @throws IOException
110 * on I/O errors.
111 * @throws MimeIOException
112 * on MIME protocol violations.
113 */
114 public Message(InputStream is, MimeEntityConfig config) throws IOException,
115 MimeIOException {
116 this(is, config, DefaultStorageProvider.getInstance());
117 }
118
119 /**
120 * Parses the specified MIME message stream into a <code>Message</code>
121 * instance using given {@link MimeEntityConfig} and {@link StorageProvider}.
122 *
123 * @param is
124 * the stream to parse.
125 * @param config
126 * {@link MimeEntityConfig} to use.
127 * @param storageProvider
128 * {@link StorageProvider} to use for storing text and binary
129 * message bodies.
130 * @throws IOException
131 * on I/O errors.
132 * @throws MimeIOException
133 * on MIME protocol violations.
134 */
135 public Message(InputStream is, MimeEntityConfig config,
136 StorageProvider storageProvider) throws IOException,
137 MimeIOException {
138 try {
139 MimeStreamParser parser = new MimeStreamParser(config);
140 parser.setContentHandler(new MessageBuilder(this, storageProvider));
141 parser.parse(is);
142 } catch (MimeException e) {
143 throw new MimeIOException(e);
144 }
145 }
146
147 /**
148 * Write the content to the given output stream using the
149 * {@link MessageWriter#DEFAULT default} message writer.
150 *
151 * @param out
152 * the output stream to write to.
153 * @throws IOException
154 * in case of an I/O error
155 * @see MessageWriter
156 */
157 public void writeTo(OutputStream out) throws IOException {
158 MessageWriter.DEFAULT.writeEntity(this, out);
159 }
160
161 /**
162 * Returns the value of the <i>Message-ID</i> header field of this message
163 * or <code>null</code> if it is not present.
164 *
165 * @return the identifier of this message.
166 */
167 public String getMessageId() {
168 Field field = obtainField(FieldName.MESSAGE_ID);
169 if (field == null)
170 return null;
171
172 return field.getBody();
173 }
174
175 /**
176 * Creates and sets a new <i>Message-ID</i> header field for this message.
177 * A <code>Header</code> is created if this message does not already have
178 * one.
179 *
180 * @param hostname
181 * host name to be included in the identifier or
182 * <code>null</code> if no host name should be included.
183 */
184 public void createMessageId(String hostname) {
185 Header header = obtainHeader();
186
187 header.setField(Fields.messageId(hostname));
188 }
189
190 /**
191 * Returns the (decoded) value of the <i>Subject</i> header field of this
192 * message or <code>null</code> if it is not present.
193 *
194 * @return the subject of this message.
195 */
196 public String getSubject() {
197 UnstructuredField field = obtainField(FieldName.SUBJECT);
198 if (field == null)
199 return null;
200
201 return field.getValue();
202 }
203
204 /**
205 * Sets the <i>Subject</i> header field for this message. The specified
206 * string may contain non-ASCII characters, in which case it gets encoded as
207 * an 'encoded-word' automatically. A <code>Header</code> is created if
208 * this message does not already have one.
209 *
210 * @param subject
211 * subject to set or <code>null</code> to remove the subject
212 * header field.
213 */
214 public void setSubject(String subject) {
215 Header header = obtainHeader();
216
217 if (subject == null) {
218 header.removeFields(FieldName.SUBJECT);
219 } else {
220 header.setField(Fields.subject(subject));
221 }
222 }
223
224 /**
225 * Returns the value of the <i>Date</i> header field of this message as
226 * <code>Date</code> object or <code>null</code> if it is not present.
227 *
228 * @return the date of this message.
229 */
230 public Date getDate() {
231 DateTimeField dateField = obtainField(FieldName.DATE);
232 if (dateField == null)
233 return null;
234
235 return dateField.getDate();
236 }
237
238 /**
239 * Sets the <i>Date</i> header field for this message. This method uses the
240 * default <code>TimeZone</code> of this host to encode the specified
241 * <code>Date</code> object into a string.
242 *
243 * @param date
244 * date to set or <code>null</code> to remove the date header
245 * field.
246 */
247 public void setDate(Date date) {
248 setDate(date, null);
249 }
250
251 /**
252 * Sets the <i>Date</i> header field for this message. The specified
253 * <code>TimeZone</code> is used to encode the specified <code>Date</code>
254 * object into a string.
255 *
256 * @param date
257 * date to set or <code>null</code> to remove the date header
258 * field.
259 * @param zone
260 * a time zone.
261 */
262 public void setDate(Date date, TimeZone zone) {
263 Header header = obtainHeader();
264
265 if (date == null) {
266 header.removeFields(FieldName.DATE);
267 } else {
268 header.setField(Fields.date(FieldName.DATE, date, zone));
269 }
270 }
271
272 /**
273 * Returns the value of the <i>Sender</i> header field of this message as
274 * <code>Mailbox</code> object or <code>null</code> if it is not
275 * present.
276 *
277 * @return the sender of this message.
278 */
279 public Mailbox getSender() {
280 return getMailbox(FieldName.SENDER);
281 }
282
283 /**
284 * Sets the <i>Sender</i> header field of this message to the specified
285 * mailbox address.
286 *
287 * @param sender
288 * address to set or <code>null</code> to remove the header
289 * field.
290 */
291 public void setSender(Mailbox sender) {
292 setMailbox(FieldName.SENDER, sender);
293 }
294
295 /**
296 * Returns the value of the <i>From</i> header field of this message as
297 * <code>MailboxList</code> object or <code>null</code> if it is not
298 * present.
299 *
300 * @return value of the from field of this message.
301 */
302 public MailboxList getFrom() {
303 return getMailboxList(FieldName.FROM);
304 }
305
306 /**
307 * Sets the <i>From</i> header field of this message to the specified
308 * mailbox address.
309 *
310 * @param from
311 * address to set or <code>null</code> to remove the header
312 * field.
313 */
314 public void setFrom(Mailbox from) {
315 setMailboxList(FieldName.FROM, from);
316 }
317
318 /**
319 * Sets the <i>From</i> header field of this message to the specified
320 * mailbox addresses.
321 *
322 * @param from
323 * addresses to set or <code>null</code> or no arguments to
324 * remove the header field.
325 */
326 public void setFrom(Mailbox... from) {
327 setMailboxList(FieldName.FROM, from);
328 }
329
330 /**
331 * Sets the <i>From</i> header field of this message to the specified
332 * mailbox addresses.
333 *
334 * @param from
335 * addresses to set or <code>null</code> or an empty collection
336 * to remove the header field.
337 */
338 public void setFrom(Collection<Mailbox> from) {
339 setMailboxList(FieldName.FROM, from);
340 }
341
342 /**
343 * Returns the value of the <i>To</i> header field of this message as
344 * <code>AddressList</code> object or <code>null</code> if it is not
345 * present.
346 *
347 * @return value of the to field of this message.
348 */
349 public AddressList getTo() {
350 return getAddressList(FieldName.TO);
351 }
352
353 /**
354 * Sets the <i>To</i> header field of this message to the specified
355 * address.
356 *
357 * @param to
358 * address to set or <code>null</code> to remove the header
359 * field.
360 */
361 public void setTo(Address to) {
362 setAddressList(FieldName.TO, to);
363 }
364
365 /**
366 * Sets the <i>To</i> header field of this message to the specified
367 * addresses.
368 *
369 * @param to
370 * addresses to set or <code>null</code> or no arguments to
371 * remove the header field.
372 */
373 public void setTo(Address... to) {
374 setAddressList(FieldName.TO, to);
375 }
376
377 /**
378 * Sets the <i>To</i> header field of this message to the specified
379 * addresses.
380 *
381 * @param to
382 * addresses to set or <code>null</code> or an empty collection
383 * to remove the header field.
384 */
385 public void setTo(Collection<Address> to) {
386 setAddressList(FieldName.TO, to);
387 }
388
389 /**
390 * Returns the value of the <i>Cc</i> header field of this message as
391 * <code>AddressList</code> object or <code>null</code> if it is not
392 * present.
393 *
394 * @return value of the cc field of this message.
395 */
396 public AddressList getCc() {
397 return getAddressList(FieldName.CC);
398 }
399
400 /**
401 * Sets the <i>Cc</i> header field of this message to the specified
402 * address.
403 *
404 * @param cc
405 * address to set or <code>null</code> to remove the header
406 * field.
407 */
408 public void setCc(Address cc) {
409 setAddressList(FieldName.CC, cc);
410 }
411
412 /**
413 * Sets the <i>Cc</i> header field of this message to the specified
414 * addresses.
415 *
416 * @param cc
417 * addresses to set or <code>null</code> or no arguments to
418 * remove the header field.
419 */
420 public void setCc(Address... cc) {
421 setAddressList(FieldName.CC, cc);
422 }
423
424 /**
425 * Sets the <i>Cc</i> header field of this message to the specified
426 * addresses.
427 *
428 * @param cc
429 * addresses to set or <code>null</code> or an empty collection
430 * to remove the header field.
431 */
432 public void setCc(Collection<Address> cc) {
433 setAddressList(FieldName.CC, cc);
434 }
435
436 /**
437 * Returns the value of the <i>Bcc</i> header field of this message as
438 * <code>AddressList</code> object or <code>null</code> if it is not
439 * present.
440 *
441 * @return value of the bcc field of this message.
442 */
443 public AddressList getBcc() {
444 return getAddressList(FieldName.BCC);
445 }
446
447 /**
448 * Sets the <i>Bcc</i> header field of this message to the specified
449 * address.
450 *
451 * @param bcc
452 * address to set or <code>null</code> to remove the header
453 * field.
454 */
455 public void setBcc(Address bcc) {
456 setAddressList(FieldName.BCC, bcc);
457 }
458
459 /**
460 * Sets the <i>Bcc</i> header field of this message to the specified
461 * addresses.
462 *
463 * @param bcc
464 * addresses to set or <code>null</code> or no arguments to
465 * remove the header field.
466 */
467 public void setBcc(Address... bcc) {
468 setAddressList(FieldName.BCC, bcc);
469 }
470
471 /**
472 * Sets the <i>Bcc</i> header field of this message to the specified
473 * addresses.
474 *
475 * @param bcc
476 * addresses to set or <code>null</code> or an empty collection
477 * to remove the header field.
478 */
479 public void setBcc(Collection<Address> bcc) {
480 setAddressList(FieldName.BCC, bcc);
481 }
482
483 /**
484 * Returns the value of the <i>Reply-To</i> header field of this message as
485 * <code>AddressList</code> object or <code>null</code> if it is not
486 * present.
487 *
488 * @return value of the reply to field of this message.
489 */
490 public AddressList getReplyTo() {
491 return getAddressList(FieldName.REPLY_TO);
492 }
493
494 /**
495 * Sets the <i>Reply-To</i> header field of this message to the specified
496 * address.
497 *
498 * @param replyTo
499 * address to set or <code>null</code> to remove the header
500 * field.
501 */
502 public void setReplyTo(Address replyTo) {
503 setAddressList(FieldName.REPLY_TO, replyTo);
504 }
505
506 /**
507 * Sets the <i>Reply-To</i> header field of this message to the specified
508 * addresses.
509 *
510 * @param replyTo
511 * addresses to set or <code>null</code> or no arguments to
512 * remove the header field.
513 */
514 public void setReplyTo(Address... replyTo) {
515 setAddressList(FieldName.REPLY_TO, replyTo);
516 }
517
518 /**
519 * Sets the <i>Reply-To</i> header field of this message to the specified
520 * addresses.
521 *
522 * @param replyTo
523 * addresses to set or <code>null</code> or an empty collection
524 * to remove the header field.
525 */
526 public void setReplyTo(Collection<Address> replyTo) {
527 setAddressList(FieldName.REPLY_TO, replyTo);
528 }
529
530 private Mailbox getMailbox(String fieldName) {
531 MailboxField field = obtainField(fieldName);
532 if (field == null)
533 return null;
534
535 return field.getMailbox();
536 }
537
538 private void setMailbox(String fieldName, Mailbox mailbox) {
539 Header header = obtainHeader();
540
541 if (mailbox == null) {
542 header.removeFields(fieldName);
543 } else {
544 header.setField(Fields.mailbox(fieldName, mailbox));
545 }
546 }
547
548 private MailboxList getMailboxList(String fieldName) {
549 MailboxListField field = obtainField(fieldName);
550 if (field == null)
551 return null;
552
553 return field.getMailboxList();
554 }
555
556 private void setMailboxList(String fieldName, Mailbox mailbox) {
557 setMailboxList(fieldName, mailbox == null ? null : Collections
558 .singleton(mailbox));
559 }
560
561 private void setMailboxList(String fieldName, Mailbox... mailboxes) {
562 setMailboxList(fieldName, mailboxes == null ? null : Arrays
563 .asList(mailboxes));
564 }
565
566 private void setMailboxList(String fieldName, Collection<Mailbox> mailboxes) {
567 Header header = obtainHeader();
568
569 if (mailboxes == null || mailboxes.isEmpty()) {
570 header.removeFields(fieldName);
571 } else {
572 header.setField(Fields.mailboxList(fieldName, mailboxes));
573 }
574 }
575
576 private AddressList getAddressList(String fieldName) {
577 AddressListField field = obtainField(fieldName);
578 if (field == null)
579 return null;
580
581 return field.getAddressList();
582 }
583
584 private void setAddressList(String fieldName, Address address) {
585 setAddressList(fieldName, address == null ? null : Collections
586 .singleton(address));
587 }
588
589 private void setAddressList(String fieldName, Address... addresses) {
590 setAddressList(fieldName, addresses == null ? null : Arrays
591 .asList(addresses));
592 }
593
594 private void setAddressList(String fieldName, Collection<Address> addresses) {
595 Header header = obtainHeader();
596
597 if (addresses == null || addresses.isEmpty()) {
598 header.removeFields(fieldName);
599 } else {
600 header.setField(Fields.addressList(fieldName, addresses));
601 }
602 }
603
604 }