View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You 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 implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.geronimo.javamail.store.imap;
19  
20  import java.io.BufferedReader;
21  import java.io.ByteArrayInputStream;
22  import java.io.ByteArrayOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.InputStreamReader;
26  import java.io.OutputStream;
27  import java.io.UnsupportedEncodingException;
28  import java.util.Date;
29  import java.util.Enumeration;
30  import java.util.List;
31  
32  import javax.activation.DataHandler;
33  
34  import javax.mail.Address; 
35  import javax.mail.FetchProfile;
36  import javax.mail.Flags;
37  import javax.mail.Folder;
38  import javax.mail.Header;
39  import javax.mail.IllegalWriteException;
40  import javax.mail.Message;
41  import javax.mail.MessagingException;
42  import javax.mail.MessageRemovedException;
43  import javax.mail.Session;
44  import javax.mail.Store;
45  import javax.mail.UIDFolder;
46  import javax.mail.event.MessageChangedEvent;
47  
48  import javax.mail.internet.InternetAddress;
49  import javax.mail.internet.InternetHeaders;
50  import javax.mail.internet.MailDateFormat;     
51  import javax.mail.internet.MimeMessage;
52  import javax.mail.internet.MimeUtility;
53  
54  import org.apache.geronimo.javamail.store.imap.connection.IMAPBody; 
55  import org.apache.geronimo.javamail.store.imap.connection.IMAPBodyStructure; 
56  import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection; 
57  import org.apache.geronimo.javamail.store.imap.connection.IMAPEnvelope;
58  import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchDataItem;
59  import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchResponse;
60  import org.apache.geronimo.javamail.store.imap.connection.IMAPInternalDate;
61  import org.apache.geronimo.javamail.store.imap.connection.IMAPInternetHeader;
62  import org.apache.geronimo.javamail.store.imap.connection.IMAPMessageSize;
63  import org.apache.geronimo.javamail.store.imap.connection.IMAPUid;
64  import org.apache.geronimo.javamail.util.MailConnection;
65  
66  /**
67   * IMAP implementation of javax.mail.internet.MimeMessage
68   *
69   * Only the most basic information is given and
70   * Message objects created here is a light-weight reference to the actual Message
71   * As per the JavaMail spec items from the actual message will get filled up on demand
72   *
73   * If some other items are obtained from the server as a result of one call, then the other
74   * details are also processed and filled in. For ex if RETR is called then header information
75   * will also be processed in addition to the content
76   *
77   * @version $Rev: 739377 $ $Date: 2009-01-30 13:57:39 -0500 (Fri, 30 Jan 2009) $
78   */
79  public class IMAPMessage extends MimeMessage {
80      
81      private static final byte[] CRLF = "\r\n".getBytes();
82      
83      // the Store we're stored in (which manages the connection and other stuff).
84      protected IMAPStore store;
85  
86      // the IMAP server sequence number (potentially updated during the life of this message object).
87      protected int sequenceNumber;
88      // the IMAP uid value;
89      protected long uid = -1;
90      // the section identifier.  This is only really used for nested messages.  The toplevel version  
91      // will be null, and each nested message will set the appropriate part identifier 
92      protected String section; 
93      // the loaded message envelope (delayed until needed)
94      protected IMAPEnvelope envelope;
95      // the body structure information (also lazy loaded).
96      protected IMAPBodyStructure bodyStructure;
97      // the IMAP INTERNALDATE value.
98      protected Date receivedDate;
99      // the size item, which is maintained separately from the body structure 
100     // as it can be retrieved without getting the body structure
101     protected int size; 
102     // turned on once we've requested the entire header set. 
103     protected boolean allHeadersRetrieved = false; 
104     // singleton date formatter for this class.
105     static protected MailDateFormat dateFormat = new MailDateFormat();
106 
107 
108     /**
109      * Contruct an IMAPMessage instance.
110      * 
111      * @param folder   The hosting folder for the message.
112      * @param store    The Store owning the article (and folder).
113      * @param msgnum   The article message number.  This is assigned by the Folder, and is unique
114      *                 for each message in the folder.  The message numbers are only valid
115      *                 as long as the Folder is open.
116      * @param sequenceNumber The IMAP server manages messages by sequence number, which is subject to
117      *                 change whenever messages are expunged.  This is the server retrieval number
118      *                 of the message, which needs to be synchronized with status updates
119      *                 sent from the server.
120      * 
121      * @exception MessagingException
122      */
123 	IMAPMessage(IMAPFolder folder, IMAPStore store, int msgnum, int sequenceNumber) {
124 		super(folder, msgnum);
125         this.sequenceNumber = sequenceNumber;
126 		this.store = store;
127         // The default constructor creates an empty Flags item.  We need to clear this out so we 
128         // know if the flags need to be fetched from the server when requested.  
129         flags = null;
130         // make sure this is a totally fresh set of headers.  We'll fill things in as we retrieve them.
131         headers = new InternetHeaders();
132 	}
133 
134 
135     /**
136      * Override for the Message class setExpunged() method to allow
137      * us to do additional cleanup for expunged messages.
138      *
139      * @param value  The new expunge setting.
140      */
141     public void setExpunged(boolean value) {
142         // super class handles most of the details
143         super.setExpunged(value);
144         // if we're now expunged, this removes us from the server message sequencing scheme, so
145         // we need to invalidate the sequence number.
146         if (isExpunged()) {
147             sequenceNumber = -1;
148         }
149     }
150     
151 
152     /**
153      * Return a copy the flags associated with this message.
154      *
155      * @return a copy of the flags for this message
156      * @throws MessagingException if there was a problem accessing the Store
157      */
158     public synchronized Flags getFlags() throws MessagingException {
159         // load the flags, if needed 
160         loadFlags(); 
161         return super.getFlags(); 
162     }
163 
164 
165     /**
166      * Check whether the supplied flag is set.
167      * The default implementation checks the flags returned by {@link #getFlags()}.
168      *
169      * @param flag the flags to check for
170      * @return true if the flags is set
171      * @throws MessagingException if there was a problem accessing the Store
172      */
173     public synchronized boolean isSet(Flags.Flag flag) throws MessagingException {
174         // load the flags, if needed 
175         loadFlags(); 
176         return super.isSet(flag); 
177     }
178 
179     /**
180      * Set or clear a flag value.
181      *
182      * @param flags  The set of flags to effect.
183      * @param set    The value to set the flag to (true or false).
184      *
185      * @exception MessagingException
186      */
187     public synchronized void setFlags(Flags flag, boolean set) throws MessagingException {
188         // make sure this is in a valid state.
189         checkValidity();
190         
191         // we need to ensure that we're the only ones with access to the folder's 
192         // message cache any time we need to talk to the server.  This needs to be 
193         // held until after we release the connection so that any pending EXPUNGE 
194         // untagged responses are processed before the next time the folder connection is 
195         // used. 
196         synchronized (folder) {
197             IMAPConnection connection = getConnection();
198 
199             try {
200                 // set the flags for this item and update the 
201                 // internal state with the new values returned from the 
202                 // server. 
203                 flags = connection.setFlags(sequenceNumber, flag, set); 
204             } finally {
205                 releaseConnection(connection); 
206             }
207         }
208     }
209 
210 
211     /**
212      * Return an InputStream instance for accessing the 
213      * message content.
214      * 
215      * @return An InputStream instance for accessing the content
216      *         (body) of the message.
217      * @exception MessagingException
218      * @see javax.mail.internet.MimeMessage#getContentStream()
219      */
220 	protected InputStream getContentStream() throws MessagingException {
221 
222         // no content loaded yet?
223         if (content == null) {
224             // make sure we're still valid
225             checkValidity();
226             // make sure the content is fully loaded
227             loadContent();
228         }
229 
230         // allow the super class to handle creating it from the loaded content.
231         return super.getContentStream();
232 	}
233 
234 
235     /**
236      * Write out the byte data to the provided output stream.
237      *
238      * @param out    The target stream.
239      *
240      * @exception IOException
241      * @exception MessagingException
242      */
243     public void writeTo(OutputStream out) throws IOException, MessagingException {                      
244         // no content loaded yet?
245         if (content == null) {
246             // make sure we're still valid
247             checkValidity();
248             // make sure the content is fully loaded
249             loadContent();
250         }
251         
252         loadHeaders();
253         
254         Enumeration e = headers.getAllHeaderLines();
255         while(e.hasMoreElements()) {
256             String line = (String)e.nextElement();
257             out.write(line.getBytes());
258             out.write(CRLF);
259         }
260         out.write(CRLF);
261         out.write(CRLF);
262         out.write(content);
263     }
264     
265 	/******************************************************************
266 	 * Following is a set of methods that deal with information in the 
267 	 * envelope.  These methods ensure the enveloper is loaded and   
268      * retrieve the information.                         
269 	 ********************************************************************/
270      
271 
272     /**
273      * Get the message "From" addresses.  This looks first at the
274      * "From" headers, and no "From" header is found, the "Sender"
275      * header is checked.  Returns null if not found.
276      *
277      * @return An array of addresses identifying the message from target.  Returns
278      *         null if this is not resolveable from the headers.
279      * @exception MessagingException
280      */
281     public Address[] getFrom() throws MessagingException {
282         // make sure we've retrieved the envelope information.
283         loadEnvelope();
284         // make sure we return a copy of the array so this can't be changed. 
285         Address[] addresses = envelope.from; 
286         if (addresses == null) {
287             return null;
288         }
289         return (Address[])addresses.clone(); 
290     }
291     
292 
293     /**
294      * Return the "Sender" header as an address.
295      *
296      * @return the "Sender" header as an address, or null if not present
297      * @throws MessagingException if there was a problem parsing the header
298      */
299     public Address getSender() throws MessagingException {
300         // make sure we've retrieved the envelope information.
301         loadEnvelope();
302         // make sure we return a copy of the array so this can't be changed. 
303         Address[] addresses = envelope.sender; 
304         if (addresses == null) {
305             return null;
306         }
307         // There's only a single sender, despite IMAP potentially returning a list
308         return addresses[0]; 
309     }
310 
311     /**
312      * Gets the recipients by type.  Returns null if there are no
313      * headers of the specified type.  Acceptable RecipientTypes are:
314      *
315      *   javax.mail.Message.RecipientType.TO
316      *   javax.mail.Message.RecipientType.CC
317      *   javax.mail.Message.RecipientType.BCC
318      *   javax.mail.internet.MimeMessage.RecipientType.NEWSGROUPS
319      *
320      * @param type   The message RecipientType identifier.
321      *
322      * @return The array of addresses for the specified recipient types.
323      * @exception MessagingException
324      */
325     public Address[] getRecipients(Message.RecipientType type) throws MessagingException {
326         // make sure we've retrieved the envelope information.
327         loadEnvelope();
328         Address[] addresses = null; 
329         
330         if (type == Message.RecipientType.TO) {
331             addresses = envelope.to; 
332         }
333         else if (type == Message.RecipientType.CC) {
334             addresses = envelope.cc; 
335         }
336         else if (type == Message.RecipientType.BCC) {
337             addresses = envelope.bcc; 
338         }
339         else {
340             // this could be a newsgroup type, which will tickle the message headers. 
341             return super.getRecipients(type);
342         }
343         // make sure we return a copy of the array so this can't be changed. 
344         if (addresses == null) {
345             return null;
346         }
347         return (Address[])addresses.clone(); 
348     }
349 
350     /**
351      * Get the ReplyTo address information.  The headers are parsed
352      * using the "mail.mime.address.strict" setting.  If the "Reply-To" header does
353      * not have any addresses, then the value of the "From" field is used.
354      *
355      * @return An array of addresses obtained from parsing the header.
356      * @exception MessagingException
357      */
358     public Address[] getReplyTo() throws MessagingException {
359         // make sure we've retrieved the envelope information.
360         loadEnvelope();
361         // make sure we return a copy of the array so this can't be changed. 
362         Address[] addresses = envelope.replyTo; 
363         if (addresses == null) {
364             return null;
365         }
366         return (Address[])addresses.clone(); 
367     }
368 
369     /**
370      * Returns the value of the "Subject" header.  If the subject
371      * is encoded as an RFC 2047 value, the value is decoded before
372      * return.  If decoding fails, the raw string value is
373      * returned.
374      *
375      * @return The String value of the subject field.
376      * @exception MessagingException
377      */
378     public String getSubject() throws MessagingException {
379         // make sure we've retrieved the envelope information.
380         loadEnvelope();
381         
382         if (envelope.subject == null) {
383             return null; 
384         }
385         // the subject could be encoded.  If there is a decoding error, 
386         // return the raw subject string. 
387         try {
388             return MimeUtility.decodeText(envelope.subject); 
389         } catch (UnsupportedEncodingException e) {
390             return envelope.subject; 
391         }
392     }
393 
394     /**
395      * Get the value of the "Date" header field.  Returns null if
396      * if the field is absent or the date is not in a parseable format.
397      *
398      * @return A Date object parsed according to RFC 822.
399      * @exception MessagingException
400      */
401     public Date getSentDate() throws MessagingException {
402         // make sure we've retrieved the envelope information.
403         loadEnvelope();
404         // just return that directly 
405         return envelope.date; 
406     }
407 
408 
409     /**
410      * Get the message received date.
411      *
412      * @return Always returns the formatted INTERNALDATE, if available.
413      * @exception MessagingException
414      */
415     public Date getReceivedDate() throws MessagingException {
416         loadEnvelope();
417         return receivedDate; 
418     }
419 
420 
421     /**
422      * Retrieve the size of the message content.  The content will
423      * be retrieved from the server, if necessary.
424      *
425      * @return The size of the content.
426      * @exception MessagingException
427      */
428 	public int getSize() throws MessagingException {
429         // make sure we've retrieved the envelope information.  We load the 
430         // size when we retrieve that. 
431         loadEnvelope();
432         return size;                          
433 	}
434     
435 
436     /**
437      * Get a line count for the IMAP message.  This is potentially
438      * stored in the Lines article header.  If not there, we return
439      * a default of -1.
440      *
441      * @return The header line count estimate, or -1 if not retrieveable.
442      * @exception MessagingException
443      */
444     public int getLineCount() throws MessagingException {
445         loadBodyStructure();
446         return bodyStructure.lines; 
447     }
448 
449     /**
450      * Return the IMAP in reply to information (retrieved with the
451      * ENVELOPE).
452      *
453      * @return The in reply to String value, if available.
454      * @exception MessagingException
455      */
456     public String getInReplyTo() throws MessagingException {
457         loadEnvelope();
458         return envelope.inReplyTo;
459     }
460 
461     /**
462      * Returns the current content type (defined in the "Content-Type"
463      * header.  If not available, "text/plain" is the default.
464      *
465      * @return The String name of the message content type.
466      * @exception MessagingException
467      */
468     public String getContentType() throws MessagingException {
469         loadBodyStructure();
470         return bodyStructure.mimeType.toString(); 
471     }
472 
473 
474     /**
475      * Tests to see if this message has a mime-type match with the
476      * given type name.
477      *
478      * @param type   The tested type name.
479      *
480      * @return If this is a type match on the primary and secondare portion of the types.
481      * @exception MessagingException
482      */
483     public boolean isMimeType(String type) throws MessagingException {
484         loadBodyStructure();
485         return bodyStructure.mimeType.match(type); 
486     }
487 
488     /**
489      * Retrieve the message "Content-Disposition" header field.
490      * This value represents how the part should be represented to
491      * the user.
492      *
493      * @return The string value of the Content-Disposition field.
494      * @exception MessagingException
495      */
496     public String getDisposition() throws MessagingException {
497         loadBodyStructure();
498         if (bodyStructure.disposition != null) {
499             return bodyStructure.disposition.getDisposition(); 
500         }
501         return null; 
502     }
503 
504     /**
505      * Decode the Content-Transfer-Encoding header to determine
506      * the transfer encoding type.
507      *
508      * @return The string name of the required encoding.
509      * @exception MessagingException
510      */
511     public String getEncoding() throws MessagingException {
512         loadBodyStructure();
513         return bodyStructure.transferEncoding; 
514     }
515 
516     /**
517      * Retrieve the value of the "Content-ID" header.  Returns null
518      * if the header does not exist.
519      *
520      * @return The current header value or null.
521      * @exception MessagingException
522      */
523     public String getContentID() throws MessagingException {
524         loadBodyStructure();
525         return bodyStructure.contentID;         
526     }
527 
528     public String getContentMD5() throws MessagingException {
529         loadBodyStructure();
530         return bodyStructure.md5Hash;         
531     }
532     
533     
534     public String getDescription() throws MessagingException {
535         loadBodyStructure();
536         
537         if (bodyStructure.contentDescription == null) {
538             return null; 
539         }
540         // the subject could be encoded.  If there is a decoding error, 
541         // return the raw subject string. 
542         try {
543             return MimeUtility.decodeText(bodyStructure.contentDescription); 
544         } catch (UnsupportedEncodingException e) {
545             return bodyStructure.contentDescription;
546         }
547     }
548 
549     /**
550      * Return the content languages associated with this 
551      * message.
552      * 
553      * @return 
554      * @exception MessagingException
555      */
556     public String[] getContentLanguage() throws MessagingException {
557         loadBodyStructure();
558         
559         if (!bodyStructure.languages.isEmpty()) {
560             return (String[])bodyStructure.languages.toArray(new String[bodyStructure.languages.size()]); 
561         }
562         return null; 
563     }
564 
565     public String getMessageID() throws MessagingException {
566         loadEnvelope();
567         return envelope.messageID; 
568     }
569     
570     public void setFrom(Address address) throws MessagingException {
571         throw new IllegalWriteException("IMAP messages are read-only");
572     }
573     
574     public void addFrom(Address[] address) throws MessagingException {
575         throw new IllegalWriteException("IMAP messages are read-only");
576     }
577     
578     public void setSender(Address address) throws MessagingException {
579         throw new IllegalWriteException("IMAP messages are read-only");
580     }
581     
582     public void setRecipients(Message.RecipientType type, Address[] addresses) throws MessagingException {
583         throw new IllegalWriteException("IMAP messages are read-only");
584     }
585     
586     public void setRecipients(Message.RecipientType type, String address) throws MessagingException {
587         throw new IllegalWriteException("IMAP messages are read-only");
588     }
589     
590     public void addRecipients(Message.RecipientType type, Address[] address) throws MessagingException {
591         throw new IllegalWriteException("IMAP messages are read-only");
592     }
593     
594     public void setReplyTo(Address[] address) throws MessagingException {
595         throw new IllegalWriteException("IMAP messages are read-only");
596     }
597     
598     public void setSubject(String subject) throws MessagingException {
599         throw new IllegalWriteException("IMAP messages are read-only");
600     }
601     
602     public void setSubject(String subject, String charset) throws MessagingException {
603         throw new IllegalWriteException("IMAP messages are read-only");
604     }
605     
606     public void setSentDate(Date sent) throws MessagingException {
607         throw new IllegalWriteException("IMAP messages are read-only");
608     }
609     
610     public void setDisposition(String disposition) throws MessagingException {
611         throw new IllegalWriteException("IMAP messages are read-only");
612     }
613     
614     public void setContentID(String cid) throws MessagingException {
615         throw new IllegalWriteException("IMAP messages are read-only");
616     }
617 
618     public void setContentMD5(String md5) throws MessagingException {
619         throw new IllegalWriteException("IMAP messages are read-only");
620     }
621 
622     public void setDescription(String description) throws MessagingException {
623         throw new IllegalWriteException("IMAP messages are read-only");
624     }
625     
626     public void setDescription(String description, String charset) throws MessagingException {
627         throw new IllegalWriteException("IMAP messages are read-only");
628     }
629 
630     public void setContentLanguage(String[] languages) throws MessagingException {
631         throw new IllegalWriteException("IMAP messages are read-only");
632     }
633     
634 
635 	/******************************************************************
636 	 * Following is a set of methods that deal with headers
637 	 * These methods are just overrides on the superclass methods to
638      * allow lazy loading of the header information.
639 	 ********************************************************************/
640 
641 	public String[] getHeader(String name) throws MessagingException {
642         loadHeaders();
643 		return headers.getHeader(name);
644 	}
645 
646 	public String getHeader(String name, String delimiter) throws MessagingException {
647         loadHeaders();
648 		return headers.getHeader(name, delimiter);
649 	}
650 
651 	public Enumeration getAllHeaders() throws MessagingException {
652         loadHeaders();
653 		return headers.getAllHeaders();
654 	}
655 
656 	public Enumeration getMatchingHeaders(String[] names)  throws MessagingException {
657         loadHeaders();
658 		return headers.getMatchingHeaders(names);
659 	}
660 
661 	public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
662         loadHeaders();
663 		return headers.getNonMatchingHeaders(names);
664 	}
665 
666 	public Enumeration getAllHeaderLines() throws MessagingException {
667         loadHeaders();
668 		return headers.getAllHeaderLines();
669 	}
670 
671 	public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
672         loadHeaders();
673 		return headers.getMatchingHeaderLines(names);
674 	}
675 
676 	public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
677         loadHeaders();
678 		return headers.getNonMatchingHeaderLines(names);
679 	}
680 
681     // the following are overrides for header modification methods.  These messages are read only,
682     // so the headers cannot be modified.
683     public void addHeader(String name, String value) throws MessagingException {
684         throw new IllegalWriteException("IMAP messages are read-only");
685     }
686 
687     public void setHeader(String name, String value) throws MessagingException {
688         throw new IllegalWriteException("IMAP messages are read-only");
689     }
690 
691 
692     public void removeHeader(String name) throws MessagingException {
693         throw new IllegalWriteException("IMAP messages are read-only");
694     }
695 
696     public void addHeaderLine(String line) throws MessagingException {
697         throw new IllegalWriteException("IMAP messages are read-only");
698     }
699 
700 	/**
701 	 * We cannot modify these messages
702 	 */
703 	public void saveChanges() throws MessagingException {
704 		throw new IllegalWriteException("IMAP messages are read-only");
705 	}
706 
707 
708     /**
709      * Utility method for synchronizing IMAP envelope information and
710      * the message headers.
711      *
712      * @param header    The target header name.
713      * @param addresses The update addresses.
714      */
715     protected void updateHeader(String header, InternetAddress[] addresses) throws MessagingException {
716         if (addresses != null) {
717             headers.addHeader(header, InternetAddress.toString(addresses));
718         }
719     }
720 
721     /**
722      * Utility method for synchronizing IMAP envelope information and
723      * the message headers.
724      *
725      * @param header    The target header name.
726      * @param address   The update address.
727      */
728     protected void updateHeader(String header, Address address) throws MessagingException {
729         if (address != null) {
730             headers.setHeader(header, address.toString());
731         }
732     }
733 
734     /**
735      * Utility method for synchronizing IMAP envelope information and
736      * the message headers.
737      *
738      * @param header    The target header name.
739      * @param value     The update value.
740      */
741     protected void updateHeader(String header, String value) throws MessagingException {
742         if (value != null) {
743             headers.setHeader(header, value);
744         }
745     }
746 
747 
748     /**
749      * Create the DataHandler object for this message.
750      *
751      * @return The DataHandler object that processes the content set for this
752      *         message.
753      * @exception MessagingException
754      */
755     public synchronized DataHandler getDataHandler() throws MessagingException {
756         // check the validity and make sure we have the body structure information. 
757         checkValidity();
758         loadBodyStructure();
759         if (dh == null) {
760             // are we working with a multipart message here?
761             if (bodyStructure.isMultipart()) {
762                 dh = new DataHandler(new IMAPMultipartDataSource(this, this, section, bodyStructure));
763                 return dh;
764             }
765             else if (bodyStructure.isAttachedMessage()) {
766                 dh = new DataHandler(new IMAPAttachedMessage(this, section, bodyStructure.nestedEnvelope, bodyStructure.nestedBody), 
767                      bodyStructure.mimeType.toString()); 
768                 return dh;
769             }
770         }
771 
772         // single part messages get handled the normal way.
773         return super.getDataHandler();
774     }
775 
776     public void setDataHandler(DataHandler content) throws MessagingException {
777         throw new IllegalWriteException("IMAP body parts are read-only");
778     }
779 
780     /**
781      * Update the message headers from an input stream.
782      *
783      * @param in     The InputStream source for the header information.
784      *
785      * @exception MessagingException
786      */
787     public void updateHeaders(InputStream in) throws MessagingException {
788         // wrap a stream around the reply data and read as headers.
789         headers = new InternetHeaders(in);
790         allHeadersRetrieved = true;
791     }
792     
793     /**
794      * Load the flag set for this message from the server. 
795      * 
796      * @exception MessagingeException
797      */
798     public void loadFlags() throws MessagingException {
799         // make sure this is in a valid state.
800         checkValidity();
801         // if the flags are already loaded, nothing to do 
802         if (flags != null) {
803             return; 
804         }
805         // we need to ensure that we're the only ones with access to the folder's 
806         // message cache any time we need to talk to the server.  This needs to be 
807         // held until after we release the connection so that any pending EXPUNGE 
808         // untagged responses are processed before the next time the folder connection is 
809         // used. 
810         synchronized (folder) {
811             IMAPConnection connection = getConnection();
812 
813             try {
814                 // fetch the flags for this item. 
815                 flags = connection.fetchFlags(sequenceNumber); 
816             } finally {
817                 releaseConnection(connection); 
818             }
819         }
820     }
821 
822 
823     /**
824      * Retrieve the message raw message headers from the IMAP server, synchronizing with the existing header set.
825      *
826      * @exception MessagingException
827      */
828     protected synchronized void loadHeaders() throws MessagingException {
829         // don't retrieve if already loaded.
830         if (allHeadersRetrieved) {
831             return;
832         }
833 
834         // make sure this is in a valid state.
835         checkValidity();
836         // we need to ensure that we're the only ones with access to the folder's 
837         // message cache any time we need to talk to the server.  This needs to be 
838         // held until after we release the connection so that any pending EXPUNGE 
839         // untagged responses are processed before the next time the folder connection is 
840         // used. 
841         synchronized (folder) {
842             IMAPConnection connection = getConnection();
843 
844             try {
845                 // get the headers and set 
846                 headers = connection.fetchHeaders(sequenceNumber, section);
847                 // we have the entire header set, not just a subset. 
848                 allHeadersRetrieved = true;                                               
849             } finally {
850                 releaseConnection(connection); 
851             }
852         }
853     }
854 
855 
856     /**
857      * Retrieve the message envelope from the IMAP server, synchronizing the headers with the
858      * information.
859      *
860      * @exception MessagingException
861      */                                
862     protected synchronized void loadEnvelope() throws MessagingException {
863         // don't retrieve if already loaded.
864         if (envelope != null) {
865             return;
866         }
867 
868         // make sure this is in a valid state.
869         checkValidity();
870         // we need to ensure that we're the only ones with access to the folder's 
871         // message cache any time we need to talk to the server.  This needs to be 
872         // held until after we release the connection so that any pending EXPUNGE 
873         // untagged responses are processed before the next time the folder connection is 
874         // used. 
875         synchronized (folder) {
876             IMAPConnection connection = getConnection();
877             try {
878                 // fetch the envelope information for this
879                 List fetches = connection.fetchEnvelope(sequenceNumber);
880                 // now process all of the fetch responses before releasing the folder lock.  
881                 // it's possible that an unsolicited update on another thread might try to 
882                 // make an update, causing a potential deadlock. 
883                 for (int i = 0; i < fetches.size(); i++) {
884                     // get the returned data items from each of the fetch responses
885                     // and process. 
886                     IMAPFetchResponse fetch = (IMAPFetchResponse)fetches.get(i);
887                     // update the internal info 
888                     updateMessageInformation(fetch); 
889                 }
890             } finally {
891                 releaseConnection(connection); 
892             }
893         }
894     }
895 
896 
897     /**
898      * Retrieve the message envelope from the IMAP server, synchronizing the headers with the
899      * information.
900      *
901      * @exception MessagingException
902      */
903     protected synchronized void updateEnvelope(IMAPEnvelope envelope) throws MessagingException {
904         // set the envelope item 
905         this.envelope = envelope; 
906 
907         // copy header type information from the envelope into the headers.
908         updateHeader("From", envelope.from);
909         if (envelope.sender != null) {
910             // we can only have a single sender, even though the envelope theoretically supports more.
911             updateHeader("Sender", envelope.sender[0]);
912         }
913         updateHeader("To", envelope.to);
914         updateHeader("Cc", envelope.cc);
915         updateHeader("Bcc", envelope.bcc);
916         updateHeader("Reply-To", envelope.replyTo);
917         // NB:  This is already in encoded form, if needed.
918         updateHeader("Subject", envelope.subject);
919         updateHeader("Message-ID", envelope.messageID);
920     }
921 
922 
923     /**
924      * Retrieve the BODYSTRUCTURE information from the IMAP server.
925      *
926      * @exception MessagingException
927      */
928     protected synchronized void loadBodyStructure() throws MessagingException {
929         // don't retrieve if already loaded.
930         if (bodyStructure != null) {
931             return;
932         }
933 
934         // make sure this is in a valid state.
935         checkValidity();
936         // we need to ensure that we're the only ones with access to the folder's 
937         // message cache any time we need to talk to the server.  This needs to be 
938         // held until after we release the connection so that any pending EXPUNGE 
939         // untagged responses are processed before the next time the folder connection is 
940         // used. 
941         synchronized (folder) {
942             IMAPConnection connection = getConnection();
943             try {
944                 // fetch the envelope information for this
945                 bodyStructure = connection.fetchBodyStructure(sequenceNumber);
946                 // go update all of the information 
947             } finally {
948                 releaseConnection(connection); 
949             }
950             
951             // update this before we release the folder lock so we can avoid 
952             // deadlock. 
953             updateBodyStructure(bodyStructure); 
954         }
955     }
956 
957 
958     /**
959      * Update the BODYSTRUCTURE information from the IMAP server.
960      *
961      * @exception MessagingException
962      */
963     protected synchronized void updateBodyStructure(IMAPBodyStructure structure) throws MessagingException {
964         // save the reference. 
965         bodyStructure = structure; 
966         // now update various headers with the information from the body structure 
967 
968         // now update header information with the body structure data.
969         if (bodyStructure.lines != -1) {
970             updateHeader("Lines", Integer.toString(bodyStructure.lines));
971         }
972 
973         // languages are a little more complicated
974         if (bodyStructure.languages != null) {
975             // this is a duplicate of what happens in the super class, but 
976             // the superclass methods call setHeader(), which we override and 
977             // throw an exception for.  We need to set the headers ourselves. 
978             if (bodyStructure.languages.size() == 1) {
979                 updateHeader("Content-Language", (String)bodyStructure.languages.get(0));
980             }
981             else {
982                 StringBuffer buf = new StringBuffer(bodyStructure.languages.size() * 20);
983                 buf.append(bodyStructure.languages.get(0));
984                 for (int i = 1; i < bodyStructure.languages.size(); i++) {
985                     buf.append(',').append(bodyStructure.languages.get(i));
986                 }
987                 updateHeader("Content-Language", buf.toString());
988             }
989         }
990 
991         updateHeader("Content-Type", bodyStructure.mimeType.toString());
992         if (bodyStructure.disposition != null) {
993             updateHeader("Content-Disposition", bodyStructure.disposition.toString());
994         }
995 
996         updateHeader("Content-Transfer-Encoding", bodyStructure.transferEncoding);
997         updateHeader("Content-ID", bodyStructure.contentID);
998         // NB:  This is already in encoded form, if needed.
999         updateHeader("Content-Description", bodyStructure.contentDescription);
1000     }
1001 
1002 
1003     /**
1004      * Load the message content into the Message object.
1005      *
1006      * @exception MessagingException
1007      */
1008     protected void loadContent() throws MessagingException {
1009         // if we've loaded this already, just return
1010         if (content != null) {
1011             return;
1012         }
1013         
1014         // we need to ensure that we're the only ones with access to the folder's 
1015         // message cache any time we need to talk to the server.  This needs to be 
1016         // held until after we release the connection so that any pending EXPUNGE 
1017         // untagged responses are processed before the next time the folder connection is 
1018         // used. 
1019         synchronized (folder) {
1020             IMAPConnection connection = getConnection();
1021             try {
1022                 // load the content from the server. 
1023                 content = connection.fetchContent(getSequenceNumber(), section); 
1024             } finally {
1025                 releaseConnection(connection); 
1026             }
1027         }
1028     }
1029 
1030     
1031     /**
1032      * Retrieve the sequence number assigned to this message.
1033      *
1034      * @return The messages assigned sequence number.  This maps back to the server's assigned number for
1035      * this message.
1036      */
1037     int getSequenceNumber() {
1038         return sequenceNumber;
1039     }
1040     
1041     /**
1042      * Set the sequence number for the message.  This 
1043      * is updated whenever messages get expunged from 
1044      * the folder. 
1045      * 
1046      * @param s      The new sequence number.
1047      */
1048     void setSequenceNumber(int s) {
1049         sequenceNumber = s; 
1050     }
1051 
1052 
1053     /**
1054      * Retrieve the message UID value.
1055      *
1056      * @return The assigned UID value, if we have the information.
1057      */
1058     long getUID() {
1059         return uid;
1060     }
1061 
1062     /**
1063      * Set the message UID value.
1064      *
1065      * @param uid    The new UID value.
1066      */
1067     void setUID(long uid) {
1068         this.uid = uid;
1069     }
1070 
1071     
1072     /**
1073      * get the current connection pool attached to the folder.  We need
1074      * to do this dynamically, to A) ensure we're only accessing an
1075      * currently open folder, and B) to make sure we're using the
1076      * correct connection attached to the folder.
1077      *
1078      * @return A connection attached to the hosting folder.
1079      */
1080     protected IMAPConnection getConnection() throws MessagingException {
1081         // the folder owns everything.
1082         return ((IMAPFolder)folder).getMessageConnection();
1083     }
1084     
1085     /**
1086      * Release the connection back to the Folder after performing an operation 
1087      * that requires a connection.
1088      * 
1089      * @param connection The previously acquired connection.
1090      */
1091     protected void releaseConnection(IMAPConnection connection) throws MessagingException {
1092         // the folder owns everything.
1093         ((IMAPFolder)folder).releaseMessageConnection(connection);
1094     }
1095 
1096 
1097     /**
1098      * Check the validity of the current message.  This ensures that
1099      * A) the folder is currently open, B) that the message has not
1100      * been expunged (after getting the latest status from the server).
1101      *
1102      * @exception MessagingException
1103      */
1104     protected void checkValidity() throws MessagingException {
1105         checkValidity(false); 
1106     }
1107 
1108 
1109     /**
1110      * Check the validity of the current message.  This ensures that
1111      * A) the folder is currently open, B) that the message has not
1112      * been expunged (after getting the latest status from the server).
1113      *
1114      * @exception MessagingException
1115      */
1116     protected void checkValidity(boolean update) throws MessagingException {
1117         // we need to ensure that we're the only ones with access to the folder's 
1118         // message cache any time we need to talk to the server.  This needs to be 
1119         // held until after we release the connection so that any pending EXPUNGE 
1120         // untagged responses are processed before the next time the folder connection is 
1121         // used. 
1122         if (update) {
1123             synchronized (folder) {
1124                 // have the connection update the folder status.  This might result in this message
1125                 // changing its state to expunged.  It might also result in an exception if the
1126                 // folder has been closed.
1127                 IMAPConnection connection = getConnection(); 
1128 
1129                 try {
1130                     connection.updateMailboxStatus();
1131                 } finally {
1132                     // this will force any expunged messages to be processed before we release 
1133                     // the lock. 
1134                     releaseConnection(connection); 
1135                 }
1136             }
1137         }
1138 
1139         // now see if we've been expunged, this is a bad op on the message.
1140         if (isExpunged()) {
1141             throw new MessageRemovedException("Illegal opertion on a deleted message");
1142         }
1143     }
1144     
1145     
1146     /**
1147      * Evaluate whether this message requires any of the information 
1148      * in a FetchProfile to be fetched from the server.  If the messages 
1149      * already contains the information in the profile, it returns false.
1150      * This allows IMAPFolder to optimize fetch() requests to just the 
1151      * messages that are missing any of the requested information.
1152      * 
1153      * NOTE:  If any of the items in the profile are missing, then this 
1154      * message will be updated with ALL of the items.  
1155      * 
1156      * @param profile The FetchProfile indicating the information that should be prefetched.
1157      * 
1158      * @return true if any of the profile information requires fetching.  false if this 
1159      *         message already contains the given information.
1160      */
1161     protected boolean evaluateFetch(FetchProfile profile) {
1162         // the fetch profile can contain a number of different item types.  Validate
1163         // whether we need any of these and return true on the first mismatch. 
1164         
1165         // the UID is a common fetch request, put it first. 
1166         if (profile.contains(UIDFolder.FetchProfileItem.UID) && uid == -1) {
1167             return true; 
1168         }
1169         if (profile.contains(FetchProfile.Item.ENVELOPE) && envelope == null) {
1170             return true; 
1171         }
1172         if (profile.contains(FetchProfile.Item.FLAGS) && flags == null) {
1173             return true; 
1174         }
1175         if (profile.contains(FetchProfile.Item.CONTENT_INFO) && bodyStructure == null) {
1176             return true; 
1177         }
1178         // The following profile items are our implementation of items that the 
1179         // Sun IMAPFolder implementation supports.  
1180         if (profile.contains(IMAPFolder.FetchProfileItem.HEADERS) && !allHeadersRetrieved) {
1181             return true; 
1182         }
1183         if (profile.contains(IMAPFolder.FetchProfileItem.SIZE) && bodyStructure.bodySize < 0) {
1184             return true; 
1185         }
1186         // last bit after checking each of the information types is to see if 
1187         // particular headers have been requested and whether those are on the 
1188         // set we do have loaded. 
1189         String [] requestedHeaders = profile.getHeaderNames(); 
1190          
1191         // ok, any missing header in the list is enough to force us to request the 
1192         // information. 
1193         for (int i = 0; i < requestedHeaders.length; i++) {
1194             if (headers.getHeader(requestedHeaders[i]) == null) {
1195                 return true; 
1196             }
1197         }
1198         // this message, at least, does not need anything fetched. 
1199         return false; 
1200     }
1201     
1202     /**
1203      * Update a message instance with information retrieved via an IMAP FETCH 
1204      * command.  The command response for this message may contain multiple pieces
1205      * that we need to process.
1206      * 
1207      * @param response The response line, which may contain multiple data items.
1208      * 
1209      * @exception MessagingException
1210      */
1211     void updateMessageInformation(IMAPFetchResponse response) throws MessagingException {
1212         // get the list of data items associated with this response.  We can have 
1213         // a large number of items returned in a single update. 
1214         List items = response.getDataItems(); 
1215         
1216         for (int i = 0; i < items.size(); i++) {
1217             IMAPFetchDataItem item = (IMAPFetchDataItem)items.get(i); 
1218             
1219             switch (item.getType()) {
1220                 // if the envelope has been requested, we'll end up with all of these items. 
1221                 case IMAPFetchDataItem.ENVELOPE:
1222                     // update the envelope and map the envelope items into the headers. 
1223                     updateEnvelope((IMAPEnvelope)item);
1224                     break;
1225                 case IMAPFetchDataItem.INTERNALDATE:
1226                     receivedDate = ((IMAPInternalDate)item).getDate();;
1227                     break;
1228                 case IMAPFetchDataItem.SIZE:
1229                     size = ((IMAPMessageSize)item).size;      
1230                     break;
1231                 case IMAPFetchDataItem.UID:
1232                     uid = ((IMAPUid)item).uid;       
1233                     // make sure the folder knows about the UID update. 
1234                     ((IMAPFolder)folder).addToUidCache(new Long(uid), this); 
1235                     break; 
1236                 case IMAPFetchDataItem.BODYSTRUCTURE: 
1237                     updateBodyStructure((IMAPBodyStructure)item); 
1238                     break; 
1239                     // a partial or full header update 
1240                 case IMAPFetchDataItem.HEADER:
1241                 {
1242                     // if we've fetched the complete set, then replace what we have 
1243                     IMAPInternetHeader h = (IMAPInternetHeader)item; 
1244                     if (h.isComplete()) {
1245                         // we've got a complete header set now. 
1246                         this.headers = h.headers;     
1247                         allHeadersRetrieved = true; 
1248                     }
1249                     else {
1250                         // need to merge the requested headers in with 
1251                         // our existing set.  We need to be careful, since we 
1252                         // don't want to add duplicates. 
1253                         mergeHeaders(h.headers); 
1254                     }
1255                 }
1256                 default:
1257             }
1258         }
1259     }
1260     
1261     
1262     /**
1263      * Merge a subset of the requested headers with our existing partial set. 
1264      * The new set will contain all headers requested from the server, plus 
1265      * any of our existing headers that were not included in the retrieved set.
1266      * 
1267      * @param newHeaders The retrieved set of headers.
1268      */
1269     protected synchronized void mergeHeaders(InternetHeaders newHeaders) {
1270         // This is sort of tricky to manage.  The input headers object is a fresh set  
1271         // retrieved from the server, but it's a subset of the headers.  Our existing set
1272         // might not be complete, but it may contain duplicates of information in the 
1273         // retrieved set, plus headers that are not in the retrieved set.  To keep from 
1274         // adding duplicates, we'll only add headers that are not in the retrieved set to 
1275         // that set.   
1276         
1277         // start by running through the list of headers 
1278         Enumeration e = headers.getAllHeaders(); 
1279         
1280         while (e.hasMoreElements()) {
1281             Header header = (Header)e.nextElement(); 
1282             // if there are no headers with this name in the new set, then 
1283             // we can add this.  Note that to add the header, we need to 
1284             // retrieve all instances by this name and add them as a unit.  
1285             // When we hit one of the duplicates again with the enumeration, 
1286             // we'll skip it then because the merge target will have everything. 
1287             if (newHeaders.getHeader(header.getName()) == null) {
1288                 // get all occurrences of this name and stuff them into the 
1289                 // new list 
1290                 String name = header.getName(); 
1291                 String[] a = headers.getHeader(name); 
1292                 for (int i = 0; i < a.length; i++) {
1293                     newHeaders.addHeader(name, a[i]); 
1294                 }
1295             }
1296         }
1297         // and replace the current header set
1298         headers = newHeaders; 
1299     }
1300 }