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     ****************************************************************/
019    
020    package org.apache.james.mime4j.message;
021    
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.io.OutputStream;
025    import java.util.Arrays;
026    import java.util.Collection;
027    import java.util.Collections;
028    import java.util.Date;
029    import java.util.TimeZone;
030    
031    import org.apache.james.mime4j.MimeException;
032    import org.apache.james.mime4j.MimeIOException;
033    import org.apache.james.mime4j.field.AddressListField;
034    import org.apache.james.mime4j.field.DateTimeField;
035    import org.apache.james.mime4j.field.FieldName;
036    import org.apache.james.mime4j.field.Fields;
037    import org.apache.james.mime4j.field.MailboxField;
038    import org.apache.james.mime4j.field.MailboxListField;
039    import org.apache.james.mime4j.field.UnstructuredField;
040    import org.apache.james.mime4j.field.address.Address;
041    import org.apache.james.mime4j.field.address.AddressList;
042    import org.apache.james.mime4j.field.address.Mailbox;
043    import org.apache.james.mime4j.field.address.MailboxList;
044    import org.apache.james.mime4j.parser.Field;
045    import org.apache.james.mime4j.parser.MimeEntityConfig;
046    import org.apache.james.mime4j.parser.MimeStreamParser;
047    import org.apache.james.mime4j.storage.DefaultStorageProvider;
048    import org.apache.james.mime4j.storage.StorageProvider;
049    
050    /**
051     * Represents a MIME message. The following code parses a stream into a
052     * <code>Message</code> object.
053     * 
054     * <pre>
055     * Message msg = new Message(new FileInputStream(&quot;mime.msg&quot;));
056     * </pre>
057     */
058    public class Message extends Entity implements Body {
059    
060        /**
061         * Creates a new empty <code>Message</code>.
062         */
063        public Message() {
064        }
065    
066        /**
067         * Creates a new <code>Message</code> from the specified
068         * <code>Message</code>. The <code>Message</code> instance is
069         * initialized with copies of header and body of the specified
070         * <code>Message</code>. The parent entity of the new message is
071         * <code>null</code>.
072         * 
073         * @param other
074         *            message to copy.
075         * @throws UnsupportedOperationException
076         *             if <code>other</code> contains a {@link SingleBody} that
077         *             does not support the {@link SingleBody#copy() copy()}
078         *             operation.
079         * @throws IllegalArgumentException
080         *             if <code>other</code> contains a <code>Body</code> that
081         *             is neither a {@link Message}, {@link Multipart} or
082         *             {@link SingleBody}.
083         */
084        public Message(Message other) {
085            super(other);
086        }
087    
088        /**
089         * Parses the specified MIME message stream into a <code>Message</code>
090         * instance.
091         * 
092         * @param is
093         *            the stream to parse.
094         * @throws IOException
095         *             on I/O errors.
096         * @throws MimeIOException
097         *             on MIME protocol violations.
098         */
099        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    }