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 }