KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > mail > imap > IMAPMessage


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the "License"). You may not use this file except
5  * in compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * glassfish/bootstrap/legal/CDDLv1.0.txt or
9  * https://glassfish.dev.java.net/public/CDDLv1.0.html.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * HEADER in each file and include the License file at
15  * glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable,
16  * add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your
18  * own identifying information: Portions Copyright [yyyy]
19  * [name of copyright owner]
20  */

21
22 /*
23  * @(#)IMAPMessage.java 1.41 06/03/24
24  *
25  * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
26  */

27
28 package com.sun.mail.imap;
29
30 import java.util.Date JavaDoc;
31 import java.io.*;
32 import java.util.Enumeration JavaDoc;
33 import java.util.Vector JavaDoc;
34 import java.util.Hashtable JavaDoc;
35
36 import javax.mail.*;
37 import javax.mail.internet.*;
38 import javax.activation.*;
39
40 import com.sun.mail.util.*;
41 import com.sun.mail.iap.*;
42 import com.sun.mail.imap.protocol.*;
43
44 /**
45  * This class implements an IMAPMessage object. <p>
46  *
47  * An IMAPMessage object starts out as a light-weight object. It gets
48  * filled-in incrementally when a request is made for some item. Or
49  * when a prefetch is done using the FetchProfile. <p>
50  *
51  * An IMAPMessage has a messageNumber and a sequenceNumber. The
52  * messageNumber is its index into its containing folder's messageCache.
53  * The sequenceNumber is its IMAP sequence-number.
54  *
55  * @version 1.41, 06/03/24
56  * @author John Mani
57  * @author Bill Shannon
58  */

59 /*
60  * The lock hierarchy is that the lock on the IMAPMessage object, if
61  * it's acquired at all, must be acquired before the message cache lock.
62  * The IMAPMessage lock protects the message flags, sort of.
63  *
64  * XXX - I'm not convinced that all fields of IMAPMessage are properly
65  * protected by locks.
66  */

67
68 public class IMAPMessage extends MimeMessage {
69     protected BODYSTRUCTURE bs; // BODYSTRUCTURE
70
protected ENVELOPE envelope; // ENVELOPE
71

72     private Date JavaDoc receivedDate; // INTERNALDATE
73
private int size = -1; // RFC822.SIZE
74

75     private boolean peek; // use BODY.PEEK when fetching content?
76

77     // this message's IMAP sequence number
78
private int seqnum;
79     // this message's IMAP UID
80
private long uid = -1;
81
82     // this message's IMAP sectionId (null for toplevel message,
83
// non-null for a nested message)
84
protected String JavaDoc sectionId;
85
86     // processed values
87
private String JavaDoc type; // Content-Type (with params)
88
private String JavaDoc subject; // decoded (Unicode) subject
89
private String JavaDoc description; // decoded (Unicode) desc
90

91     // Indicates that we've loaded *all* headers for this message
92
private boolean headersLoaded = false;
93
94     /* Hashtable of names of headers we've loaded from the server.
95      * Used in isHeaderLoaded() and setHeaderLoaded() to keep track
96      * of those headers we've attempted to load from the server. We
97      * need this table of names to avoid multiple attempts at loading
98      * headers that don't exist for a particular message.
99      *
100      * Could this somehow be included in the InternetHeaders object ??
101      */

102     private Hashtable JavaDoc loadedHeaders;
103
104     // This is our Envelope
105
private static String JavaDoc EnvelopeCmd = "ENVELOPE INTERNALDATE RFC822.SIZE";
106
107     /**
108      * Constructor.
109      */

110     protected IMAPMessage(IMAPFolder folder, int msgnum, int seqnum) {
111     super(folder, msgnum);
112     this.seqnum = seqnum;
113     flags = null;
114     }
115
116     /**
117      * Constructor, for use by IMAPNestedMessage.
118      */

119     protected IMAPMessage(Session session) {
120     super(session);
121     }
122
123     /**
124      * Get this message's folder's protocol connection.
125      * Throws FolderClosedException, if the protocol connection
126      * is not available.
127      *
128      * ASSERT: Must hold the messageCacheLock.
129      */

130     protected IMAPProtocol getProtocol() throws FolderClosedException {
131     IMAPProtocol p = ((IMAPFolder)folder).protocol;
132     if (p == null)
133         throw new FolderClosedException(folder);
134     else
135         return p;
136     }
137
138     /**
139      * Get the messageCacheLock, associated with this Message's
140      * Folder.
141      */

142     protected Object JavaDoc getMessageCacheLock() {
143     return ((IMAPFolder)folder).messageCacheLock;
144     }
145
146     /**
147      * Get this message's IMAP sequence number.
148      *
149      * ASSERT: This method must be called only when holding the
150      * messageCacheLock.
151      */

152     protected int getSequenceNumber() {
153     return seqnum;
154     }
155
156     /**
157      * Set this message's IMAP sequence number.
158      *
159      * ASSERT: This method must be called only when holding the
160      * messageCacheLock.
161      */

162     protected void setSequenceNumber(int seqnum) {
163     this.seqnum = seqnum;
164     }
165
166     /**
167      * Wrapper around the protected method Message.setMessageNumber() to
168      * make that method accessible to IMAPFolder.
169      */

170     protected void setMessageNumber(int msgnum) {
171     super.setMessageNumber(msgnum);
172     }
173
174     protected long getUID() {
175     return uid;
176     }
177
178     protected void setUID(long uid) {
179     this.uid = uid;
180     }
181
182     // overrides super.setExpunged()
183
protected void setExpunged(boolean set) {
184     super.setExpunged(set);
185     seqnum = -1;
186     }
187
188     // Convenience routine
189
protected void checkExpunged() throws MessageRemovedException {
190     if (expunged)
191         throw new MessageRemovedException();
192     }
193
194     /**
195      * Do a NOOP to force any untagged EXPUNGE responses
196      * and then check if this message is expunged.
197      */

198     protected void forceCheckExpunged()
199             throws MessageRemovedException, FolderClosedException {
200     synchronized (getMessageCacheLock()) {
201         try {
202         getProtocol().noop();
203         } catch (ConnectionException cex) {
204         throw new FolderClosedException(folder, cex.getMessage());
205         } catch (ProtocolException pex) {
206         // ignore it
207
}
208     }
209     if (expunged)
210         throw new MessageRemovedException();
211     }
212
213     // Return the block size for FETCH requests
214
protected int getFetchBlockSize() {
215     return ((IMAPStore)folder.getStore()).getFetchBlockSize();
216     }
217
218     /**
219      * Get the "From" attribute.
220      */

221     public Address[] getFrom() throws MessagingException {
222     checkExpunged();
223     loadEnvelope();
224     return aaclone(envelope.from);
225     }
226
227     public void setFrom(Address address) throws MessagingException {
228     throw new IllegalWriteException("IMAPMessage is read-only");
229     }
230
231     public void addFrom(Address[] addresses) throws MessagingException {
232     throw new IllegalWriteException("IMAPMessage is read-only");
233     }
234     
235     /**
236      * Get the "Sender" attribute.
237      */

238     public Address getSender() throws MessagingException {
239     checkExpunged();
240     loadEnvelope();
241     if (envelope.sender != null)
242         return (envelope.sender)[0]; // there can be only one sender
243
else
244         return null;
245     }
246     
247
248     public void setSender(Address address) throws MessagingException {
249     throw new IllegalWriteException("IMAPMessage is read-only");
250     }
251
252     /**
253      * Get the desired Recipient type.
254      */

255     public Address[] getRecipients(Message.RecipientType type)
256                 throws MessagingException {
257     checkExpunged();
258     loadEnvelope();
259
260     if (type == Message.RecipientType.TO)
261         return aaclone(envelope.to);
262     else if (type == Message.RecipientType.CC)
263         return aaclone(envelope.cc);
264     else if (type == Message.RecipientType.BCC)
265         return aaclone(envelope.bcc);
266     else
267         return super.getRecipients(type);
268     }
269
270     public void setRecipients(Message.RecipientType type, Address[] addresses)
271             throws MessagingException {
272     throw new IllegalWriteException("IMAPMessage is read-only");
273     }
274
275     public void addRecipients(Message.RecipientType type, Address[] addresses)
276             throws MessagingException {
277     throw new IllegalWriteException("IMAPMessage is read-only");
278     }
279
280     /**
281      * Get the ReplyTo addresses.
282      */

283     public Address[] getReplyTo() throws MessagingException {
284     checkExpunged();
285     loadEnvelope();
286     return aaclone(envelope.replyTo);
287     }
288
289     public void setReplyTo(Address[] addresses) throws MessagingException {
290     throw new IllegalWriteException("IMAPMessage is read-only");
291     }
292
293     /**
294      * Get the decoded subject.
295      */

296     public String JavaDoc getSubject() throws MessagingException {
297     checkExpunged();
298
299     if (subject != null) // already cached ?
300
return subject;
301
302     loadEnvelope();
303     if (envelope.subject == null) // no subject
304
return null;
305
306     // Cache and return the decoded value.
307
try {
308         subject = MimeUtility.decodeText(envelope.subject);
309     } catch (UnsupportedEncodingException ex) {
310         subject = envelope.subject;
311     }
312
313     return subject;
314     }
315
316     public void setSubject(String JavaDoc subject, String JavaDoc charset)
317         throws MessagingException {
318     throw new IllegalWriteException("IMAPMessage is read-only");
319     }
320
321     /**
322      * Get the SentDate.
323      */

324     public Date JavaDoc getSentDate() throws MessagingException {
325     checkExpunged();
326     loadEnvelope();
327     if (envelope.date == null)
328         return null;
329     else
330         return new Date JavaDoc(envelope.date.getTime());
331     }
332
333     public void setSentDate(Date JavaDoc d) throws MessagingException {
334     throw new IllegalWriteException("IMAPMessage is read-only");
335     }
336
337     /**
338      * Get the recieved date (INTERNALDATE)
339      */

340     public Date JavaDoc getReceivedDate() throws MessagingException {
341     checkExpunged();
342     loadEnvelope();
343     if (receivedDate == null)
344         return null;
345     else
346         return new Date JavaDoc(receivedDate.getTime());
347     }
348
349     /**
350      * Get the message size. <p>
351      *
352      * Note that this returns RFC822.SIZE. That is, it's the
353      * size of the whole message, header and body included.
354      */

355     public int getSize() throws MessagingException {
356     checkExpunged();
357     if (size == -1)
358         loadEnvelope(); // XXX - could just fetch the size
359
return size;
360     }
361
362     /**
363      * Get the total number of lines. <p>
364      *
365      * Returns the "body_fld_lines" field from the
366      * BODYSTRUCTURE. Note that this field is available
367      * only for text/plain and message/rfc822 types
368      */

369     public int getLineCount() throws MessagingException {
370     checkExpunged();
371     loadBODYSTRUCTURE();
372     return bs.lines;
373     }
374
375     /**
376      * Get the content language.
377      */

378     public String JavaDoc[] getContentLangauge() throws MessagingException {
379         checkExpunged();
380         loadBODYSTRUCTURE();
381         if (bs.language != null)
382         return (String JavaDoc[])(bs.language).clone();
383         else
384         return null;
385     }
386  
387     public void setContentLanguage(String JavaDoc[] languages)
388                 throws MessagingException {
389         throw new IllegalWriteException("IMAPMessage is read-only");
390     }
391  
392     /**
393      * Get the In-Reply-To header.
394      *
395      * @since JavaMail 1.3.3
396      */

397     public String JavaDoc getInReplyTo() throws MessagingException {
398         checkExpunged();
399         loadEnvelope();
400         return envelope.inReplyTo;
401     }
402  
403     /**
404      * Get the Content-Type.
405      *
406      * Generate this header from the BODYSTRUCTURE. Append parameters
407      * as well.
408      */

409     public String JavaDoc getContentType() throws MessagingException {
410     checkExpunged();
411
412     // If we haven't cached the type yet ..
413
if (type == null) {
414         loadBODYSTRUCTURE();
415         // generate content-type from BODYSTRUCTURE
416
ContentType ct = new ContentType(bs.type, bs.subtype, bs.cParams);
417         type = ct.toString();
418     }
419     return type;
420     }
421
422     /**
423      * Get the Content-Disposition.
424      */

425     public String JavaDoc getDisposition() throws MessagingException {
426     checkExpunged();
427     loadBODYSTRUCTURE();
428     return bs.disposition;
429     }
430
431     public void setDisposition(String JavaDoc disposition) throws MessagingException {
432     throw new IllegalWriteException("IMAPMessage is read-only");
433     }
434
435     /**
436      * Get the Content-Transfer-Encoding.
437      */

438     public String JavaDoc getEncoding() throws MessagingException {
439     checkExpunged();
440     loadBODYSTRUCTURE();
441     return bs.encoding;
442     }
443
444     /**
445      * Get the Content-ID.
446      */

447     public String JavaDoc getContentID() throws MessagingException {
448     checkExpunged();
449     loadBODYSTRUCTURE();
450     return bs.id;
451     }
452
453     public void setContentID(String JavaDoc cid) throws MessagingException {
454     throw new IllegalWriteException("IMAPMessage is read-only");
455     }
456
457     /**
458      * Get the Content-MD5.
459      */

460     public String JavaDoc getContentMD5() throws MessagingException {
461     checkExpunged();
462     loadBODYSTRUCTURE();
463     return bs.md5;
464     }
465
466     public void setContentMD5(String JavaDoc md5) throws MessagingException {
467     throw new IllegalWriteException("IMAPMessage is read-only");
468     }
469
470     /**
471      * Get the decoded Content-Description.
472      */

473     public String JavaDoc getDescription() throws MessagingException {
474     checkExpunged();
475
476     if (description != null) // cached value ?
477
return description;
478     
479     loadBODYSTRUCTURE();
480     if (bs.description == null)
481         return null;
482     
483     try {
484         description = MimeUtility.decodeText(bs.description);
485     } catch (UnsupportedEncodingException ex) {
486         description = bs.description;
487     }
488
489     return description;
490     }
491
492     public void setDescription(String JavaDoc description, String JavaDoc charset)
493             throws MessagingException {
494     throw new IllegalWriteException("IMAPMessage is read-only");
495     }
496
497     /**
498      * Get the Message-ID.
499      */

500     public String JavaDoc getMessageID() throws MessagingException {
501     checkExpunged();
502     loadEnvelope();
503     return envelope.messageId;
504     }
505
506     /**
507      * Get the "filename" Disposition parameter. (Only available in
508      * IMAP4rev1). If thats not available, get the "name" ContentType
509      * parameter.
510      */

511     public String JavaDoc getFileName() throws MessagingException {
512     checkExpunged();
513
514     String JavaDoc filename = null;
515     loadBODYSTRUCTURE();
516
517     if (bs.dParams != null)
518         filename = bs.dParams.get("filename");
519     if (filename == null && bs.cParams != null)
520         filename = bs.cParams.get("name");
521     return filename;
522     }
523
524     public void setFileName(String JavaDoc filename) throws MessagingException {
525     throw new IllegalWriteException("IMAPMessage is read-only");
526     }
527
528     /**
529      * Get all the bytes for this message. Overrides getContentStream()
530      * in MimeMessage. This method is ultimately used by the DataHandler
531      * to obtain the input stream for this message.
532      *
533      * @see javax.mail.internet.MimeMessage#getContentStream
534      */

535     protected InputStream getContentStream() throws MessagingException {
536     InputStream is = null;
537     boolean pk = getPeek(); // get before acquiring message cache lock
538

539         // Acquire MessageCacheLock, to freeze seqnum.
540
synchronized(getMessageCacheLock()) {
541
542         IMAPProtocol p = getProtocol();
543         if (p.isREV1() && (getFetchBlockSize() != -1)) // IMAP4rev1
544
return new IMAPInputStream(this, toSection("TEXT"),
545                        bs != null ? bs.size : -1, pk);
546
547             // This message could be expunged when we were waiting
548
// to acquire the lock ...
549
checkExpunged();
550
551         try {
552         if (p.isREV1()) {
553             BODY b;
554             if (pk)
555             b = p.peekBody(getSequenceNumber(), toSection("TEXT"));
556             else
557             b = p.fetchBody(getSequenceNumber(), toSection("TEXT"));
558             if (b != null)
559             is = b.getByteArrayInputStream();
560         } else {
561             RFC822DATA rd = p.fetchRFC822(getSequenceNumber(), "TEXT");
562             if (rd != null)
563             is = rd.getByteArrayInputStream();
564         }
565         } catch (ConnectionException cex) {
566         throw new FolderClosedException(folder, cex.getMessage());
567         } catch (ProtocolException pex) {
568         forceCheckExpunged();
569         throw new MessagingException(pex.getMessage(), pex);
570         }
571     }
572
573     if (is == null)
574         throw new MessagingException("No content");
575     else
576         return is;
577     }
578
579     /**
580      * Get the DataHandler object for this message.
581      */

582     public synchronized DataHandler getDataHandler()
583         throws MessagingException {
584     checkExpunged();
585
586     if (dh == null) {
587         loadBODYSTRUCTURE();
588         if (type == null) { // type not yet computed
589
// generate content-type from BODYSTRUCTURE
590
ContentType ct = new ContentType(bs.type, bs.subtype,
591                          bs.cParams);
592         type = ct.toString();
593         }
594
595         /* Special-case Multipart and Nested content. All other
596          * cases are handled by the superclass.
597          */

598         if (bs.isMulti())
599         dh = new DataHandler(
600             new IMAPMultipartDataSource(this, bs.bodies,
601                             sectionId, this)
602              );
603         else if (bs.isNested() && getProtocol().isREV1())
604         /* Nested messages are handled specially only for
605          * IMAP4rev1. IMAP4 doesn't provide enough support to
606          * FETCH the components of nested messages
607          */

608         dh = new DataHandler(
609                 new IMAPNestedMessage(this,
610                 bs.bodies[0],
611                 bs.envelope,
612                 sectionId == null ? "1" : sectionId + ".1"),
613                 type
614              );
615     }
616
617     return super.getDataHandler();
618     }
619
620     public void setDataHandler(DataHandler content)
621             throws MessagingException {
622     throw new IllegalWriteException("IMAPMessage is read-only");
623     }
624
625     /**
626      * Write out the bytes into the given outputstream.
627      */

628     public void writeTo(OutputStream os)
629                 throws IOException, MessagingException {
630     InputStream is = null;
631     boolean pk = getPeek(); // get before acquiring message cache lock
632

633         // Acquire MessageCacheLock, to freeze seqnum.
634
synchronized(getMessageCacheLock()) {
635         checkExpunged(); // insure this message is not expunged
636

637         IMAPProtocol p = getProtocol();
638         try {
639         if (p.isREV1()) {
640             BODY b;
641             if (pk)
642             b = p.peekBody(getSequenceNumber(), sectionId);
643             else
644             b = p.fetchBody(getSequenceNumber(), sectionId);
645             if (b != null)
646             is = b.getByteArrayInputStream();
647         } else {
648             RFC822DATA rd = p.fetchRFC822(getSequenceNumber(), null);
649             if (rd != null)
650             is = rd.getByteArrayInputStream();
651         }
652         } catch (ConnectionException cex) {
653         throw new FolderClosedException(folder, cex.getMessage());
654         } catch (ProtocolException pex) {
655         forceCheckExpunged();
656         throw new MessagingException(pex.getMessage(), pex);
657         }
658     }
659
660     if (is == null)
661         throw new MessagingException("No content");
662     
663     // write out the bytes
664
byte[] bytes = new byte[1024];
665     int count;
666     while ((count = is.read(bytes)) != -1)
667         os.write(bytes, 0, count);
668     }
669
670     /**
671      * Get the named header.
672      */

673     public String JavaDoc[] getHeader(String JavaDoc name) throws MessagingException {
674     checkExpunged();
675
676     if (isHeaderLoaded(name)) // already loaded ?
677
return headers.getHeader(name);
678
679     // Load this particular header
680
InputStream is = null;
681
682         // Acquire MessageCacheLock, to freeze seqnum.
683
synchronized(getMessageCacheLock()) {
684
685             // This message could be expunged when we were waiting
686
// to acquire the lock ...
687
checkExpunged();
688
689         IMAPProtocol p = getProtocol();
690         try {
691         if (p.isREV1()) {
692             BODY b = p.peekBody(getSequenceNumber(),
693                 toSection("HEADER.FIELDS (" + name + ")")
694                  );
695             if (b != null)
696             is = b.getByteArrayInputStream();
697         } else {
698             RFC822DATA rd = p.fetchRFC822(getSequenceNumber(),
699                     "HEADER.LINES (" + name + ")");
700             if (rd != null)
701             is = rd.getByteArrayInputStream();
702         }
703         } catch (ConnectionException cex) {
704         throw new FolderClosedException(folder, cex.getMessage());
705         } catch (ProtocolException pex) {
706         forceCheckExpunged();
707         throw new MessagingException(pex.getMessage(), pex);
708         }
709     }
710
711     // if we get this far without "is" being set, something has gone
712
// wrong; prevent a later NullPointerException and return null here
713
if (is == null)
714         return null;
715
716     if (headers == null)
717         headers = new InternetHeaders();
718     headers.load(is); // load this header into the Headers object.
719
setHeaderLoaded(name); // Mark this header as loaded
720

721     return headers.getHeader(name);
722     }
723
724     /**
725      * Get the named header.
726      */

727     public String JavaDoc getHeader(String JavaDoc name, String JavaDoc delimiter)
728             throws MessagingException {
729     checkExpunged();
730
731     // force the header to be loaded by invoking getHeader(name)
732
if (getHeader(name) == null)
733         return null;
734     return headers.getHeader(name, delimiter);
735     }
736
737     public void setHeader(String JavaDoc name, String JavaDoc value)
738             throws MessagingException {
739     throw new IllegalWriteException("IMAPMessage is read-only");
740     }
741
742     public void addHeader(String JavaDoc name, String JavaDoc value)
743             throws MessagingException {
744     throw new IllegalWriteException("IMAPMessage is read-only");
745     }
746         
747     public void removeHeader(String JavaDoc name)
748             throws MessagingException {
749     throw new IllegalWriteException("IMAPMessage is read-only");
750     }
751
752     /**
753      * Get all headers.
754      */

755     public Enumeration JavaDoc getAllHeaders() throws MessagingException {
756     checkExpunged();
757     loadHeaders();
758     return super.getAllHeaders();
759     }
760
761     /**
762      * Get matching headers.
763      */

764     public Enumeration JavaDoc getMatchingHeaders(String JavaDoc[] names)
765             throws MessagingException {
766     checkExpunged();
767     loadHeaders();
768     return super.getMatchingHeaders(names);
769     }
770
771     /**
772      * Get non-matching headers.
773      */

774     public Enumeration JavaDoc getNonMatchingHeaders(String JavaDoc[] names)
775             throws MessagingException {
776     checkExpunged();
777     loadHeaders();
778     return super.getNonMatchingHeaders(names);
779     }
780
781     public void addHeaderLine(String JavaDoc line) throws MessagingException {
782     throw new IllegalWriteException("IMAPMessage is read-only");
783     }
784
785     /**
786      * Get all header-lines.
787      */

788     public Enumeration JavaDoc getAllHeaderLines() throws MessagingException {
789     checkExpunged();
790     loadHeaders();
791     return super.getAllHeaderLines();
792     }
793
794     /**
795      * Get all matching header-lines.
796      */

797     public Enumeration JavaDoc getMatchingHeaderLines(String JavaDoc[] names)
798             throws MessagingException {
799     checkExpunged();
800     loadHeaders();
801     return super.getMatchingHeaderLines(names);
802     }
803
804     /**
805      * Get all non-matching headerlines.
806      */

807     public Enumeration JavaDoc getNonMatchingHeaderLines(String JavaDoc[] names)
808             throws MessagingException {
809     checkExpunged();
810     loadHeaders();
811     return super.getNonMatchingHeaderLines(names);
812     }
813
814     /**
815      * Get the Flags for this message.
816      */

817     public synchronized Flags getFlags() throws MessagingException {
818     checkExpunged();
819     loadFlags();
820     return super.getFlags();
821     }
822
823     /**
824      * Test if the given Flags are set in this message.
825      */

826     public synchronized boolean isSet(Flags.Flag flag)
827                 throws MessagingException {
828     checkExpunged();
829     loadFlags();
830     return super.isSet(flag);
831     }
832
833     /**
834      * Set/Unset the given flags in this message.
835      */

836     public synchronized void setFlags(Flags flag, boolean set)
837             throws MessagingException {
838         // Acquire MessageCacheLock, to freeze seqnum.
839
synchronized(getMessageCacheLock()) {
840         checkExpunged(); // Insure that this message is not expunged
841

842         try {
843         getProtocol().storeFlags(getSequenceNumber(), flag, set);
844         } catch (ConnectionException cex) {
845         throw new FolderClosedException(folder, cex.getMessage());
846         } catch (ProtocolException pex) {
847         throw new MessagingException(pex.getMessage(), pex);
848         }
849     }
850     }
851
852     /**
853      * Set whether or not to use the PEEK variant of FETCH when
854      * fetching message content.
855      *
856      * @since JavaMail 1.3.3
857      */

858     public synchronized void setPeek(boolean peek) {
859     this.peek = peek;
860     }
861
862     /**
863      * Get whether or not to use the PEEK variant of FETCH when
864      * fetching message content.
865      *
866      * @since JavaMail 1.3.3
867      */

868     public synchronized boolean getPeek() {
869     return peek;
870     }
871
872     /**
873      * Invalidate cached header and envelope information for this
874      * message. Subsequent accesses of this information will
875      * cause it to be fetched from the server.
876      *
877      * @since JavaMail 1.3.3
878      */

879     public synchronized void invalidateHeaders() {
880     headersLoaded = false;
881     loadedHeaders = null;
882     envelope = null;
883     bs = null;
884     receivedDate = null;
885     size = -1;
886     type = null;
887     subject = null;
888     description = null;
889     }
890
891     /**
892      * The prefetch method. Called from IMAPFolder.fetch()
893      */

894     static void fetch(IMAPFolder folder, Message[] msgs,
895               FetchProfile fp) throws MessagingException {
896
897     /* This class implements the test to be done on each
898      * message in the folder. The test is to check whether the
899      * message has already cached all the items requested in the
900      * FetchProfile. If any item is missing, the test succeeds and
901      * breaks out.
902      */

903     class FetchProfileCondition implements Utility.Condition {
904         private boolean needEnvelope = false;
905         private boolean needFlags = false;
906         private boolean needBodyStructure = false;
907         private boolean needUID = false;
908         private boolean needHeaders = false;
909         private boolean needSize = false;
910         private String JavaDoc[] hdrs = null;
911
912         public FetchProfileCondition(FetchProfile fp) {
913         if (fp.contains(FetchProfile.Item.ENVELOPE))
914             needEnvelope = true;
915         if (fp.contains(FetchProfile.Item.FLAGS))
916             needFlags = true;
917         if (fp.contains(FetchProfile.Item.CONTENT_INFO))
918             needBodyStructure = true;
919         if (fp.contains(UIDFolder.FetchProfileItem.UID))
920             needUID = true;
921         if (fp.contains(IMAPFolder.FetchProfileItem.HEADERS))
922             needHeaders = true;
923         if (fp.contains(IMAPFolder.FetchProfileItem.SIZE))
924             needSize = true;
925         hdrs = fp.getHeaderNames();
926         }
927
928         // The actual test.
929
public boolean test(IMAPMessage m) {
930         if (needEnvelope && m._getEnvelope() == null)
931             return true; // no envelope
932
if (needFlags && m._getFlags() == null)
933             return true; // no flags
934
if (needBodyStructure && m._getBodyStructure() == null)
935             return true; // no BODYSTRUCTURE
936
if (needUID && m.getUID() == -1) // no UID
937
return true;
938         if (needHeaders && !m.headersLoaded) // no headers
939
return true;
940         if (needSize && m.size == -1) // no size
941
return true;
942
943         // Is the desired header present ?
944
for (int i = 0; i < hdrs.length; i++) {
945             if (!m.isHeaderLoaded(hdrs[i]))
946             return true; // Nope, return
947
}
948
949         return false;
950         }
951     }
952
953     StringBuffer JavaDoc command = new StringBuffer JavaDoc();
954     boolean first = true;
955     boolean allHeaders = false;
956
957     if (fp.contains(FetchProfile.Item.ENVELOPE)) {
958         command.append(EnvelopeCmd);
959         first = false;
960     }
961     if (fp.contains(FetchProfile.Item.FLAGS)) {
962         command.append(first ? "FLAGS" : " FLAGS");
963         first = false;
964     }
965     if (fp.contains(FetchProfile.Item.CONTENT_INFO)) {
966         command.append(first ? "BODYSTRUCTURE" : " BODYSTRUCTURE");
967         first = false;
968     }
969     if (fp.contains(UIDFolder.FetchProfileItem.UID)) {
970         command.append(first ? "UID" : " UID");
971         first = false;
972     }
973     if (fp.contains(IMAPFolder.FetchProfileItem.HEADERS)) {
974         allHeaders = true;
975         if (folder.protocol.isREV1())
976         command.append(first ?
977                 "BODY.PEEK[HEADER]" : " BODY.PEEK[HEADER]");
978         else
979         command.append(first ? "RFC822.HEADER" : " RFC822.HEADER");
980         first = false;
981     }
982     if (fp.contains(IMAPFolder.FetchProfileItem.SIZE)) {
983         command.append(first ? "RFC822.SIZE" : " RFC822.SIZE");
984         first = false;
985     }
986
987     // if we're not fetching all headers, fetch individual headers
988
String JavaDoc[] hdrs = null;
989     if (!allHeaders) {
990         hdrs = fp.getHeaderNames();
991         if (hdrs.length > 0) {
992         if (!first)
993             command.append(" ");
994         command.append(craftHeaderCmd(folder.protocol, hdrs));
995         }
996     }
997
998     Utility.Condition condition = new FetchProfileCondition(fp);
999
1000        // Acquire the Folder's MessageCacheLock.
1001
synchronized(folder.messageCacheLock) {
1002
1003        // Apply the test, and get the sequence-number set for
1004
// the messages that need to be prefetched.
1005
MessageSet[] msgsets = Utility.toMessageSet(msgs, condition);
1006
1007        if (msgsets == null)
1008        // We already have what we need.
1009
return;
1010
1011        Response[] r = null;
1012        Vector JavaDoc v = new Vector JavaDoc(); // to collect non-FETCH responses &
1013
// unsolicited FETCH FLAG responses
1014
try {
1015        r = folder.protocol.fetch(msgsets, command.toString());
1016        } catch (ConnectionException cex) {
1017        throw new FolderClosedException(folder, cex.getMessage());
1018        } catch (CommandFailedException cfx) {
1019        // Ignore these, as per RFC 2180
1020
} catch (ProtocolException pex) {
1021        throw new MessagingException(pex.getMessage(), pex);
1022        }
1023
1024        if (r == null)
1025        return;
1026       
1027        for (int i = 0; i < r.length; i++) {
1028        if (r[i] == null)
1029            continue;
1030        if (!(r[i] instanceof FetchResponse)) {
1031            v.addElement(r[i]); // Unsolicited Non-FETCH response
1032
continue;
1033        }
1034
1035        // Got a FetchResponse.
1036
FetchResponse f = (FetchResponse)r[i];
1037        // Get the corresponding message.
1038
IMAPMessage msg = folder.getMessageBySeqNumber(f.getNumber());
1039
1040        int count = f.getItemCount();
1041        boolean unsolicitedFlags = false;
1042
1043        for (int j = 0; j < count; j++) {
1044            Item item = f.getItem(j);
1045
1046            // Check for the FLAGS item
1047
if (item instanceof Flags) {
1048            if (!fp.contains(FetchProfile.Item.FLAGS) ||
1049                msg == null)
1050                // Ok, Unsolicited FLAGS update.
1051
unsolicitedFlags = true;
1052            else
1053                msg.flags = (Flags)item;
1054            }
1055
1056            // Check for ENVELOPE items
1057
else if (item instanceof ENVELOPE)
1058            msg.envelope = (ENVELOPE)item;
1059            else if (item instanceof INTERNALDATE)
1060            msg.receivedDate = ((INTERNALDATE)item).getDate();
1061            else if (item instanceof RFC822SIZE)
1062            msg.size = ((RFC822SIZE)item).size;
1063
1064            // Check for the BODYSTRUCTURE item
1065
else if (item instanceof BODYSTRUCTURE)
1066            msg.bs = (BODYSTRUCTURE)item;
1067            // Check for the UID item
1068
else if (item instanceof UID) {
1069            UID u = (UID)item;
1070            msg.uid = u.uid; // set uid
1071
// add entry into uid table
1072
if (folder.uidTable == null)
1073                folder.uidTable = new Hashtable JavaDoc();
1074            folder.uidTable.put(new Long JavaDoc(u.uid), msg);
1075            }
1076
1077            // Check for header items
1078
else if (item instanceof RFC822DATA ||
1079                 item instanceof BODY) {
1080            InputStream headerStream;
1081            if (item instanceof RFC822DATA) // IMAP4
1082
headerStream =
1083                ((RFC822DATA)item).getByteArrayInputStream();
1084            else // IMAP4rev1
1085
headerStream =
1086                ((BODY)item).getByteArrayInputStream();
1087            
1088            // Load the obtained headers.
1089
InternetHeaders h = new InternetHeaders();
1090            h.load(headerStream);
1091            if (msg.headers == null || allHeaders)
1092                msg.headers = h;
1093            else {
1094                /*
1095                 * This is really painful. A second fetch
1096                 * of the same headers (which might occur because
1097                 * a new header was added to the set requested)
1098                 * will return headers we already know about.
1099                 * In this case, only load the headers we haven't
1100                 * seen before to avoid adding duplicates of
1101                 * headers we already have.
1102                 */

1103                Enumeration JavaDoc e = h.getAllHeaders();
1104                while (e.hasMoreElements()) {
1105                Header he = (Header)e.nextElement();
1106                if (!msg.isHeaderLoaded(he.getName()))
1107                    msg.headers.addHeader(
1108                        he.getName(), he.getValue());
1109                }
1110            }
1111
1112            // if we asked for all headers, assume we got them
1113
if (allHeaders)
1114                msg.headersLoaded = true;
1115            else {
1116                // Mark all headers we asked for as 'loaded'
1117
for (int k = 0; k < hdrs.length; k++)
1118                msg.setHeaderLoaded(hdrs[k]);
1119            }
1120            }
1121        }
1122
1123        // If this response contains any unsolicited FLAGS
1124
// add it to the unsolicited response vector
1125
if (unsolicitedFlags)
1126            v.addElement(f);
1127        }
1128
1129        // Dispatch any unsolicited responses
1130
int size = v.size();
1131        if (size != 0) {
1132        Response[] responses = new Response[size];
1133        v.copyInto(responses);
1134        folder.handleResponses(responses);
1135        }
1136
1137    } // Release messageCacheLock
1138
}
1139
1140    /*
1141     * Load the Envelope for this message.
1142     */

1143    private synchronized void loadEnvelope() throws MessagingException {
1144    if (envelope != null) // already loaded
1145
return;
1146
1147    IMAPProtocol p = getProtocol();
1148    Response[] r = null;
1149
1150    // Acquire MessageCacheLock, to freeze seqnum.
1151
synchronized(getMessageCacheLock()) {
1152
1153    checkExpunged(); // Insure that this message is not expunged
1154

1155    int seqnum = getSequenceNumber();
1156    try {
1157        r = p.fetch(seqnum, EnvelopeCmd);
1158
1159        for (int i = 0; i < r.length; i++) {
1160        // If this response is NOT a FetchResponse or if it does
1161
// not match our seqnum, skip.
1162
if (r[i] == null ||
1163            !(r[i] instanceof FetchResponse) ||
1164            ((FetchResponse)r[i]).getNumber() != seqnum)
1165            continue;
1166
1167        FetchResponse f = (FetchResponse)r[i];
1168        
1169        // Look for the Envelope items.
1170
int count = f.getItemCount();
1171        for (int j = 0; j < count; j++) {
1172            Item item = f.getItem(j);
1173            
1174            if (item instanceof ENVELOPE)
1175            envelope = (ENVELOPE)item;
1176            else if (item instanceof INTERNALDATE)
1177            receivedDate = ((INTERNALDATE)item).getDate();
1178            else if (item instanceof RFC822SIZE)
1179            size = ((RFC822SIZE)item).size;
1180        }
1181        }
1182
1183        // ((IMAPFolder)folder).handleResponses(r);
1184
p.notifyResponseHandlers(r);
1185        p.handleResult(r[r.length - 1]);
1186    } catch (ConnectionException cex) {
1187        throw new FolderClosedException(folder, cex.getMessage());
1188    } catch (ProtocolException pex) {
1189        forceCheckExpunged();
1190        throw new MessagingException(pex.getMessage(), pex);
1191    }
1192
1193    } // Release MessageCacheLock
1194

1195    if (envelope == null)
1196        throw new MessagingException("Failed to load IMAP envelope");
1197    }
1198
1199    private static String JavaDoc craftHeaderCmd(IMAPProtocol p, String JavaDoc[] hdrs) {
1200    StringBuffer JavaDoc sb;
1201
1202    if (p.isREV1())
1203        sb = new StringBuffer JavaDoc("BODY.PEEK[HEADER.FIELDS (");
1204    else
1205        sb = new StringBuffer JavaDoc("RFC822.HEADER.LINES (");
1206
1207    for (int i = 0; i < hdrs.length; i++) {
1208        if (i > 0)
1209        sb.append(" ");
1210        sb.append(hdrs[i]);
1211    }
1212
1213    if (p.isREV1())
1214        sb.append(")]");
1215    else
1216        sb.append(")");
1217    
1218    return sb.toString();
1219    }
1220
1221    /*
1222     * Load the BODYSTRUCTURE
1223     */

1224    private synchronized void loadBODYSTRUCTURE()
1225        throws MessagingException {
1226    if (bs != null) // already loaded
1227
return;
1228
1229    // Acquire MessageCacheLock, to freeze seqnum.
1230
synchronized(getMessageCacheLock()) {
1231        // This message could be expunged when we were waiting
1232
// to acquire the lock ...
1233
checkExpunged();
1234
1235        try {
1236        bs = getProtocol().fetchBodyStructure(getSequenceNumber());
1237        } catch (ConnectionException cex) {
1238        throw new FolderClosedException(folder, cex.getMessage());
1239        } catch (ProtocolException pex) {
1240        forceCheckExpunged();
1241        throw new MessagingException(pex.getMessage(), pex);
1242        }
1243        if (bs == null)
1244        throw new MessagingException("Unable to load BODYSTRUCTURE");
1245    }
1246    }
1247
1248    /*
1249     * Load all headers.
1250     */

1251    private synchronized void loadHeaders() throws MessagingException {
1252    if (headersLoaded)
1253        return;
1254
1255    InputStream is = null;
1256
1257    // Acquire MessageCacheLock, to freeze seqnum.
1258
synchronized (getMessageCacheLock()) {
1259
1260        // This message could be expunged when we were waiting
1261
// to acquire the lock ...
1262
checkExpunged();
1263
1264        IMAPProtocol p = getProtocol();
1265        try {
1266        if (p.isREV1()) {
1267            BODY b = p.peekBody(getSequenceNumber(),
1268                     toSection("HEADER"));
1269            if (b != null)
1270            is = b.getByteArrayInputStream();
1271        } else {
1272            RFC822DATA rd = p.fetchRFC822(getSequenceNumber(),
1273                          "HEADER");
1274            if (rd != null)
1275            is = rd.getByteArrayInputStream();
1276        }
1277        } catch (ConnectionException cex) {
1278        throw new FolderClosedException(folder, cex.getMessage());
1279        } catch (ProtocolException pex) {
1280        forceCheckExpunged();
1281        throw new MessagingException(pex.getMessage(), pex);
1282        }
1283    } // Release MessageCacheLock
1284

1285    if (is == null)
1286        throw new MessagingException("Cannot load header");
1287    headers = new InternetHeaders(is);
1288    headersLoaded = true;
1289    }
1290
1291    /*
1292     * Load this message's Flags
1293     */

1294    private synchronized void loadFlags() throws MessagingException {
1295    if (flags != null)
1296        return;
1297    
1298    // Acquire MessageCacheLock, to freeze seqnum.
1299
synchronized(getMessageCacheLock()) {
1300
1301        // This message could be expunged when we were waiting
1302
// to acquire the lock ...
1303
checkExpunged();
1304
1305        try {
1306        flags = getProtocol().fetchFlags(getSequenceNumber());
1307        } catch (ConnectionException cex) {
1308        throw new FolderClosedException(folder, cex.getMessage());
1309        } catch (ProtocolException pex) {
1310        forceCheckExpunged();
1311        throw new MessagingException(pex.getMessage(), pex);
1312        }
1313    } // Release MessageCacheLock
1314
}
1315
1316    /*
1317     * Check if the given header was ever loaded from the server
1318     */

1319    private boolean isHeaderLoaded(String JavaDoc name) {
1320    if (headersLoaded) // All headers for this message have been loaded
1321
return true;
1322    
1323    return (loadedHeaders != null) ?
1324        loadedHeaders.containsKey(name.toUpperCase()) : false;
1325    }
1326
1327    /*
1328     * Mark that the given headers have been loaded from the server.
1329     */

1330    private void setHeaderLoaded(String JavaDoc name) {
1331    if (loadedHeaders == null)
1332        loadedHeaders = new Hashtable JavaDoc(1);
1333    loadedHeaders.put(name.toUpperCase(), name);
1334    }
1335
1336    /*
1337     * Convert the given FETCH item identifier to the approriate
1338     * section-string for this message.
1339     */

1340    private String JavaDoc toSection(String JavaDoc what) {
1341    if (sectionId == null)
1342        return what;
1343    else
1344        return sectionId + "." + what;
1345    }
1346
1347    /*
1348     * Clone an array of InternetAddresses.
1349     */

1350    private InternetAddress[] aaclone(InternetAddress[] aa) {
1351    if (aa == null)
1352        return null;
1353    else
1354        return (InternetAddress[])aa.clone();
1355    }
1356
1357    private Flags _getFlags() {
1358    return flags;
1359    }
1360
1361    private ENVELOPE _getEnvelope() {
1362    return envelope;
1363    }
1364
1365    private BODYSTRUCTURE _getBodyStructure() {
1366    return bs;
1367    }
1368
1369    /***********************************************************
1370     * accessor routines to make available certain private/protected
1371     * fields to other classes in this package.
1372     ***********************************************************/

1373
1374    /*
1375     * Called by IMAPFolder.
1376     * Must not be synchronized.
1377     */

1378    void _setFlags(Flags flags) {
1379    this.flags = flags;
1380    }
1381
1382    /*
1383     * Called by IMAPNestedMessage.
1384     */

1385    Session _getSession() {
1386    return session;
1387    }
1388}
1389
Popular Tags