Skip to content

Package: IMAPFolder$2

IMAPFolder$2

nameinstructionbranchcomplexitylinemethod
doCommand(IMAPProtocol)
M: 39 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
{...}
M: 15 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package org.eclipse.angus.mail.imap;
18:
19: import jakarta.mail.FetchProfile;
20: import jakarta.mail.Flags;
21: import jakarta.mail.Folder;
22: import jakarta.mail.FolderClosedException;
23: import jakarta.mail.FolderNotFoundException;
24: import jakarta.mail.Message;
25: import jakarta.mail.MessageRemovedException;
26: import jakarta.mail.MessagingException;
27: import jakarta.mail.Quota;
28: import jakarta.mail.ReadOnlyFolderException;
29: import jakarta.mail.StoreClosedException;
30: import jakarta.mail.UIDFolder;
31: import jakarta.mail.event.ConnectionEvent;
32: import jakarta.mail.event.FolderEvent;
33: import jakarta.mail.event.MailEvent;
34: import jakarta.mail.event.MessageChangedEvent;
35: import jakarta.mail.event.MessageCountListener;
36: import jakarta.mail.internet.MimeMessage;
37: import jakarta.mail.search.FlagTerm;
38: import jakarta.mail.search.SearchException;
39: import jakarta.mail.search.SearchTerm;
40: import org.eclipse.angus.mail.iap.BadCommandException;
41: import org.eclipse.angus.mail.iap.CommandFailedException;
42: import org.eclipse.angus.mail.iap.ConnectionException;
43: import org.eclipse.angus.mail.iap.Literal;
44: import org.eclipse.angus.mail.iap.ProtocolException;
45: import org.eclipse.angus.mail.iap.Response;
46: import org.eclipse.angus.mail.iap.ResponseHandler;
47: import org.eclipse.angus.mail.imap.protocol.FLAGS;
48: import org.eclipse.angus.mail.imap.protocol.FetchItem;
49: import org.eclipse.angus.mail.imap.protocol.FetchResponse;
50: import org.eclipse.angus.mail.imap.protocol.IMAPProtocol;
51: import org.eclipse.angus.mail.imap.protocol.IMAPResponse;
52: import org.eclipse.angus.mail.imap.protocol.Item;
53: import org.eclipse.angus.mail.imap.protocol.ListInfo;
54: import org.eclipse.angus.mail.imap.protocol.MODSEQ;
55: import org.eclipse.angus.mail.imap.protocol.MailboxInfo;
56: import org.eclipse.angus.mail.imap.protocol.MessageSet;
57: import org.eclipse.angus.mail.imap.protocol.Status;
58: import org.eclipse.angus.mail.imap.protocol.UID;
59: import org.eclipse.angus.mail.imap.protocol.UIDSet;
60: import org.eclipse.angus.mail.util.CRLFOutputStream;
61: import org.eclipse.angus.mail.util.MailLogger;
62:
63: import java.io.IOException;
64: import java.io.InterruptedIOException;
65: import java.io.OutputStream;
66: import java.net.SocketTimeoutException;
67: import java.nio.channels.SocketChannel;
68: import java.util.ArrayList;
69: import java.util.Date;
70: import java.util.Hashtable;
71: import java.util.List;
72: import java.util.Map;
73: import java.util.NoSuchElementException;
74: import java.util.logging.Level;
75:
76: /**
77: * This class implements an IMAP folder. <p>
78: *
79: * A closed IMAPFolder object shares a protocol connection with its IMAPStore
80: * object. When the folder is opened, it gets its own protocol connection. <p>
81: *
82: * Applications that need to make use of IMAP-specific features may cast
83: * a <code>Folder</code> object to an <code>IMAPFolder</code> object and
84: * use the methods on this class. <p>
85: *
86: * The {@link #getQuota getQuota} and
87: * {@link #setQuota setQuota} methods support the IMAP QUOTA extension.
88: * Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A>
89: * for more information. <p>
90: *
91: * The {@link #getACL getACL}, {@link #addACL addACL},
92: * {@link #removeACL removeACL}, {@link #addRights addRights},
93: * {@link #removeRights removeRights}, {@link #listRights listRights}, and
94: * {@link #myRights myRights} methods support the IMAP ACL extension.
95: * Refer to <A HREF="http://www.ietf.org/rfc/rfc2086.txt">RFC 2086</A>
96: * for more information. <p>
97: *
98: * The {@link #getSortedMessages getSortedMessages}
99: * methods support the IMAP SORT extension.
100: * Refer to <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A>
101: * for more information. <p>
102: *
103: * The {@link #open(int, ResyncData) open(int,ResyncData)}
104: * method and {@link ResyncData ResyncData} class supports
105: * the IMAP CONDSTORE and QRESYNC extensions.
106: * Refer to <A HREF="http://www.ietf.org/rfc/rfc4551.txt">RFC 4551</A>
107: * and <A HREF="http://www.ietf.org/rfc/rfc5162.txt">RFC 5162</A>
108: * for more information. <p>
109: *
110: * The {@link #doCommand doCommand} method and
111: * {@link IMAPFolder.ProtocolCommand IMAPFolder.ProtocolCommand}
112: * interface support use of arbitrary IMAP protocol commands. <p>
113: *
114: * See the <a href="package-summary.html">org.eclipse.angus.mail.imap</a> package
115: * documentation for further information on the IMAP protocol provider. <p>
116: *
117: * <strong>WARNING:</strong> The APIs unique to this class should be
118: * considered <strong>EXPERIMENTAL</strong>. They may be changed in the
119: * future in ways that are incompatible with applications using the
120: * current APIs.
121: *
122: * @author John Mani
123: * @author Bill Shannon
124: * @author Jim Glennon
125: */
126:
127: /*
128: * The folder object itself serves as a lock for the folder's state
129: * EXCEPT for the message cache (see below), typically by using
130: * synchronized methods. When checking that a folder is open or
131: * closed, the folder's lock must be held. It's important that the
132: * folder's lock is acquired before the messageCacheLock (see below).
133: * Thus, the locking hierarchy is that the folder lock, while optional,
134: * must be acquired before the messageCacheLock, if it's acquired at
135: * all. Be especially careful of callbacks that occur while holding
136: * the messageCacheLock into (e.g.) superclass Folder methods that are
137: * synchronized. Note that methods in IMAPMessage will acquire the
138: * messageCacheLock without acquiring the folder lock. <p>
139: *
140: * When a folder is opened, it creates a messageCache (a Vector) of
141: * empty IMAPMessage objects. Each Message has a messageNumber - which
142: * is its index into the messageCache, and a sequenceNumber - which is
143: * its IMAP sequence-number. All operations on a Message which involve
144: * communication with the server, use the message's sequenceNumber. <p>
145: *
146: * The most important thing to note here is that the server can send
147: * unsolicited EXPUNGE notifications as part of the responses for "most"
148: * commands. Refer RFC 3501, sections 5.3 & 5.5 for gory details. Also,
149: * the server sends these notifications AFTER the message has been
150: * expunged. And once a message is expunged, the sequence-numbers of
151: * those messages after the expunged one are renumbered. This essentially
152: * means that the mapping between *any* Message and its sequence-number
153: * can change in the period when a IMAP command is issued and its responses
154: * are processed. Hence we impose a strict locking model as follows: <p>
155: *
156: * We define one mutex per folder - this is just a Java Object (named
157: * messageCacheLock). Any time a command is to be issued to the IMAP
158: * server (i.e., anytime the corresponding IMAPProtocol method is
159: * invoked), follow the below style:
160: *
161: *        synchronized (messageCacheLock) { // ACQUIRE LOCK
162: *         issue command ()
163: *
164: *         // The response processing is typically done within
165: *         // the handleResponse() callback. A few commands (Fetch,
166: *         // Expunge) return *all* responses and hence their
167: *         // processing is done here itself. Now, as part of the
168: *         // processing unsolicited EXPUNGE responses, we renumber
169: *         // the necessary sequence-numbers. Thus the renumbering
170: *         // happens within this critical-region, surrounded by
171: *         // locks.
172: *         process responses ()
173: *        } // RELEASE LOCK
174: *
175: * This technique is used both by methods in IMAPFolder and by methods
176: * in IMAPMessage and other classes that operate on data in the folder.
177: * Note that holding the messageCacheLock has the side effect of
178: * preventing the folder from being closed, and thus ensuring that the
179: * folder's protocol object is still valid. The protocol object should
180: * only be accessed while holding the messageCacheLock (except for calls
181: * to IMAPProtocol.isREV1(), which don't need to be protected because it
182: * doesn't access the server).
183: *
184: * Note that interactions with the Store's protocol connection do
185: * not have to be protected as above, since the Store's protocol is
186: * never in a "meaningful" SELECT-ed state.
187: */
188:
189: public class IMAPFolder extends Folder implements UIDFolder, ResponseHandler {
190:
191: protected volatile String fullName; // full name
192: protected String name; // name
193: protected int type; // folder type.
194: protected char separator; // separator
195: protected Flags availableFlags; // available flags
196: protected Flags permanentFlags; // permanent flags
197: protected volatile boolean exists; // whether this folder really exists ?
198: protected boolean isNamespace = false; // folder is a namespace name
199: protected volatile String[] attributes;// name attributes from LIST response
200:
201: protected volatile IMAPProtocol protocol; // this folder's protocol object
202: protected MessageCache messageCache;// message cache
203: // accessor lock for message cache
204: protected final Object messageCacheLock = new Object();
205:
206: protected Hashtable<Long, IMAPMessage> uidTable; // UID->Message hashtable
207:
208: /* An IMAP delimiter is a 7bit US-ASCII character. (except NUL).
209: * We use '\uffff' (a non 7bit character) to indicate that we havent
210: * yet determined what the separator character is.
211: * We use '\u0000' (NUL) to indicate that no separator character
212: * exists, i.e., a flat hierarchy
213: */
214: static final protected char UNKNOWN_SEPARATOR = '\uffff';
215:
216: private volatile boolean opened = false; // is this folder opened ?
217:
218: /* This field tracks the state of this folder. If the folder is closed
219: * due to external causes (i.e, not thru the close() method), then
220: * this field will remain false. If the folder is closed thru the
221: * close() method, then this field is set to true.
222: *
223: * If reallyClosed is false, then a FolderClosedException is
224: * generated when a method is invoked on any Messaging object
225: * owned by this folder. If reallyClosed is true, then the
226: * IllegalStateException runtime exception is thrown.
227: */
228: private boolean reallyClosed = true;
229:
230: /*
231: * The idleState field supports the IDLE command.
232: * Normally when executing an IMAP command we hold the
233: * messageCacheLock and often the folder lock (see above).
234: * While executing the IDLE command we can't hold either
235: * of these locks or it would prevent other threads from
236: * entering Folder methods even far enough to check whether
237: * an IDLE command is in progress. We need to check before
238: * issuing another command so that we can abort the IDLE
239: * command.
240: *
241: * The idleState field is protected by the messageCacheLock.
242: * The RUNNING state is the normal state and means no IDLE
243: * command is in progress. The IDLE state means we've issued
244: * an IDLE command and are reading responses. The ABORTING
245: * state means we've sent the DONE continuation command and
246: * are waiting for the thread running the IDLE command to
247: * break out of its read loop.
248: *
249: * When an IDLE command is in progress, the thread calling
250: * the idle method will be reading from the IMAP connection
251: * while holding neither the folder lock nor the messageCacheLock.
252: * It's obviously critical that no other thread try to send a
253: * command or read from the connection while in this state.
254: * However, other threads can send the DONE continuation
255: * command that will cause the server to break out of the IDLE
256: * loop and send the ending tag response to the IDLE command.
257: * The thread in the idle method that's reading the responses
258: * from the IDLE command will see this ending response and
259: * complete the idle method, setting the idleState field back
260: * to RUNNING, and notifying any threads waiting to use the
261: * connection.
262: *
263: * All uses of the IMAP connection (IMAPProtocol object) must
264: * be done while holding the messageCacheLock and must be
265: * preceeded by a check to make sure an IDLE command is not
266: * running, and abort the IDLE command if necessary. While
267: * waiting for the IDLE command to complete, these other threads
268: * will give up the messageCacheLock, but might still be holding
269: * the folder lock. This check is done by the getProtocol()
270: * method, resulting in a typical usage pattern of:
271: *
272: *         synchronized (messageCacheLock) {
273: *                IMAPProtocol p = getProtocol();        // may block waiting for IDLE
274: *                // ... use protocol
275: *         }
276: */
277: private static final int RUNNING = 0; // not doing IDLE command
278: private static final int IDLE = 1; // IDLE command in effect
279: private static final int ABORTING = 2; // IDLE command aborting
280: private int idleState = RUNNING;
281: private IdleManager idleManager;
282:
283: private volatile int total = -1; // total number of messages in the
284: // message cache
285: private volatile int recent = -1; // number of recent messages
286: private int realTotal = -1; // total number of messages on
287: // the server
288: private long uidvalidity = -1; // UIDValidity
289: private long uidnext = -1; // UIDNext
290: private boolean uidNotSticky = false; // RFC 4315
291: private volatile long highestmodseq = -1; // RFC 4551 - CONDSTORE
292: private boolean doExpungeNotification = true; // used in expunge handler
293:
294: private Status cachedStatus = null;
295: private long cachedStatusTime = 0;
296:
297: private boolean hasMessageCountListener = false; // optimize notification
298:
299: protected MailLogger logger;
300: private MailLogger connectionPoolLogger;
301:
302: /**
303: * A fetch profile item for fetching headers.
304: * This inner class extends the <code>FetchProfile.Item</code>
305: * class to add new FetchProfile item types, specific to IMAPFolders.
306: *
307: * @see FetchProfile
308: */
309: public static class FetchProfileItem extends FetchProfile.Item {
310: protected FetchProfileItem(String name) {
311: super(name);
312: }
313:
314: /**
315: * HEADERS is a fetch profile item that can be included in a
316: * <code>FetchProfile</code> during a fetch request to a Folder.
317: * This item indicates that the headers for messages in the specified
318: * range are desired to be prefetched. <p>
319: *
320: * An example of how a client uses this is below:
321: * <blockquote><pre>
322: *
323: *         FetchProfile fp = new FetchProfile();
324: *         fp.add(IMAPFolder.FetchProfileItem.HEADERS);
325: *         folder.fetch(msgs, fp);
326: *
327: * </pre></blockquote>
328: */
329: public static final FetchProfileItem HEADERS =
330: new FetchProfileItem("HEADERS");
331:
332: /**
333: * SIZE is a fetch profile item that can be included in a
334: * <code>FetchProfile</code> during a fetch request to a Folder.
335: * This item indicates that the sizes of the messages in the specified
336: * range are desired to be prefetched. <p>
337: *
338: * SIZE was moved to FetchProfile.Item in JavaMail 1.5.
339: *
340: * @deprecated
341: */
342: @Deprecated
343: public static final FetchProfileItem SIZE =
344: new FetchProfileItem("SIZE");
345:
346: /**
347: * MESSAGE is a fetch profile item that can be included in a
348: * <code>FetchProfile</code> during a fetch request to a Folder.
349: * This item indicates that the entire messages (headers and body,
350: * including all "attachments") in the specified
351: * range are desired to be prefetched. Note that the entire message
352: * content is cached in memory while the Folder is open. The cached
353: * message will be parsed locally to return header information and
354: * message content. <p>
355: *
356: * An example of how a client uses this is below:
357: * <blockquote><pre>
358: *
359: *         FetchProfile fp = new FetchProfile();
360: *         fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
361: *         folder.fetch(msgs, fp);
362: *
363: * </pre></blockquote>
364: *
365: * @since JavaMail 1.5.2
366: */
367: public static final FetchProfileItem MESSAGE =
368: new FetchProfileItem("MESSAGE");
369:
370: /**
371: * INTERNALDATE is a fetch profile item that can be included in a
372: * <code>FetchProfile</code> during a fetch request to a Folder.
373: * This item indicates that the IMAP INTERNALDATE values
374: * (received date) of the messages in the specified
375: * range are desired to be prefetched. <p>
376: *
377: * An example of how a client uses this is below:
378: * <blockquote><pre>
379: *
380: *         FetchProfile fp = new FetchProfile();
381: *         fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
382: *         folder.fetch(msgs, fp);
383: *
384: * </pre></blockquote>
385: *
386: * @since JavaMail 1.5.5
387: */
388: public static final FetchProfileItem INTERNALDATE =
389: new FetchProfileItem("INTERNALDATE");
390: }
391:
392: /**
393: * Constructor used to create a possibly non-existent folder.
394: *
395: * @param fullName fullname of this folder
396: * @param separator the default separator character for this
397: * folder's namespace
398: * @param store the Store
399: * @param isNamespace if this folder represents a namespace
400: */
401: protected IMAPFolder(String fullName, char separator, IMAPStore store,
402: Boolean isNamespace) {
403: super(store);
404: if (fullName == null)
405: throw new NullPointerException("Folder name is null");
406: this.fullName = fullName;
407: this.separator = separator;
408: logger = new MailLogger(this.getClass(), "DEBUG IMAP",
409: store.getSession().getDebug(), store.getSession().getDebugOut());
410: connectionPoolLogger = store.getConnectionPoolLogger();
411:
412: /*
413: * Work around apparent bug in Exchange. Exchange
414: * will return a name of "Public Folders/" from
415: * LIST "%".
416: *
417: * If name has one separator, and it's at the end,
418: * assume this is a namespace name and treat it
419: * accordingly. Usually this will happen as a result
420: * of the list method, but this also allows getFolder
421: * to work with namespace names.
422: */
423: this.isNamespace = false;
424: if (separator != UNKNOWN_SEPARATOR && separator != '\0') {
425: int i = this.fullName.indexOf(separator);
426: if (i > 0 && i == this.fullName.length() - 1) {
427: this.fullName = this.fullName.substring(0, i);
428: this.isNamespace = true;
429: }
430: }
431:
432: // if we were given a value, override default chosen above
433: if (isNamespace != null)
434: this.isNamespace = isNamespace.booleanValue();
435: }
436:
437: /**
438: * Constructor used to create an existing folder.
439: *
440: * @param li the ListInfo for this folder
441: * @param store the store containing this folder
442: */
443: protected IMAPFolder(ListInfo li, IMAPStore store) {
444: this(li.name, li.separator, store, null);
445:
446: if (li.hasInferiors)
447: type |= HOLDS_FOLDERS;
448: if (li.canOpen)
449: type |= HOLDS_MESSAGES;
450: exists = true;
451: attributes = li.attrs;
452: }
453:
454: /*
455: * Ensure that this folder exists. If 'exists' has been set to true,
456: * we don't attempt to validate it with the server again. Note that
457: * this can result in a possible loss of sync with the server.
458: * ASSERT: Must be called with this folder's synchronization lock held.
459: */
460: protected void checkExists() throws MessagingException {
461: // If the boolean field 'exists' is false, check with the
462: // server by invoking exists() ..
463: if (!exists && !exists())
464: throw new FolderNotFoundException(
465: this, fullName + " not found");
466: }
467:
468: /*
469: * Ensure the folder is closed.
470: * ASSERT: Must be called with this folder's synchronization lock held.
471: */
472: protected void checkClosed() {
473: if (opened)
474: throw new IllegalStateException(
475: "This operation is not allowed on an open folder"
476: );
477: }
478:
479: /*
480: * Ensure the folder is open.
481: * ASSERT: Must be called with this folder's synchronization lock held.
482: */
483: protected void checkOpened() throws FolderClosedException {
484: assert Thread.holdsLock(this);
485: if (!opened) {
486: if (reallyClosed)
487: throw new IllegalStateException(
488: "This operation is not allowed on a closed folder"
489: );
490: else // Folder was closed "implicitly"
491: throw new FolderClosedException(this,
492: "Lost folder connection to server"
493: );
494: }
495: }
496:
497: /*
498: * Check that the given message number is within the range
499: * of messages present in this folder. If the message
500: * number is out of range, we ping the server to obtain any
501: * pending new message notifications from the server.
502: */
503: protected void checkRange(int msgno) throws MessagingException {
504: if (msgno < 1) // message-numbers start at 1
505: throw new IndexOutOfBoundsException("message number < 1");
506:
507: if (msgno <= total)
508: return;
509:
510: // Out of range, let's ping the server and see if
511: // the server has more messages for us.
512:
513: synchronized (messageCacheLock) { // Acquire lock
514: try {
515: keepConnectionAlive(false);
516: } catch (ConnectionException cex) {
517: // Oops, lost connection
518: throw new FolderClosedException(this, cex.getMessage());
519: } catch (ProtocolException pex) {
520: throw new MessagingException(pex.getMessage(), pex);
521: }
522: } // Release lock
523:
524: if (msgno > total) // Still out of range ? Throw up ...
525: throw new IndexOutOfBoundsException(msgno + " > " + total);
526: }
527:
528: /*
529: * Check whether the given flags are supported by this server,
530: * and also verify that the folder allows setting flags.
531: */
532: private void checkFlags(Flags flags) throws MessagingException {
533: assert Thread.holdsLock(this);
534: if (mode != READ_WRITE)
535: throw new IllegalStateException(
536: "Cannot change flags on READ_ONLY folder: " + fullName
537: );
538:         /*
539:         if (!availableFlags.contains(flags))
540:          throw new MessagingException(
541:                 "These flags are not supported by this implementation"
542:                 );
543:         */
544: }
545:
546: /**
547: * Get the name of this folder.
548: */
549: @Override
550: public synchronized String getName() {
551: /* Return the last component of this Folder's full name.
552: * Folder components are delimited by the separator character.
553: */
554: if (name == null) {
555: try {
556: name = fullName.substring(
557: fullName.lastIndexOf(getSeparator()) + 1
558: );
559: } catch (MessagingException mex) {
560: }
561: }
562: return name;
563: }
564:
565: /**
566: * Get the fullname of this folder.
567: */
568: @Override
569: public String getFullName() {
570: return fullName;
571: }
572:
573: /**
574: * Get this folder's parent.
575: */
576: @Override
577: public synchronized Folder getParent() throws MessagingException {
578: char c = getSeparator();
579: int index;
580: if ((index = fullName.lastIndexOf(c)) != -1)
581: return ((IMAPStore) store).newIMAPFolder(
582: fullName.substring(0, index), c);
583: else
584: return new DefaultFolder((IMAPStore) store);
585: }
586:
587: /**
588: * Check whether this folder really exists on the server.
589: */
590: @Override
591: public synchronized boolean exists() throws MessagingException {
592: // Check whether this folder exists ..
593: ListInfo[] li = null;
594: final String lname;
595: if (isNamespace && separator != '\0')
596: lname = fullName + separator;
597: else
598: lname = fullName;
599:
600: li = (ListInfo[]) doCommand(new ProtocolCommand() {
601: @Override
602: public Object doCommand(IMAPProtocol p) throws ProtocolException {
603: return p.list("", lname);
604: }
605: });
606:
607: if (li != null) {
608: int i = findName(li, lname);
609: fullName = li[i].name;
610: separator = li[i].separator;
611: int len = fullName.length();
612: if (separator != '\0' && len > 0 &&
613: fullName.charAt(len - 1) == separator) {
614: fullName = fullName.substring(0, len - 1);
615: }
616: type = 0;
617: if (li[i].hasInferiors)
618: type |= HOLDS_FOLDERS;
619: if (li[i].canOpen)
620: type |= HOLDS_MESSAGES;
621: exists = true;
622: attributes = li[i].attrs;
623: } else {
624: exists = opened;
625: attributes = null;
626: }
627:
628: return exists;
629: }
630:
631: /**
632: * Which entry in <code>li</code> matches <code>lname</code>?
633: * If the name contains wildcards, more than one entry may be
634: * returned.
635: */
636: private int findName(ListInfo[] li, String lname) {
637: int i;
638: // if the name contains a wildcard, there might be more than one
639: for (i = 0; i < li.length; i++) {
640: if (li[i].name.equals(lname))
641: break;
642: }
643: if (i >= li.length) { // nothing matched exactly
644: // XXX - possibly should fail? But what if server
645: // is case insensitive and returns the preferred
646: // case of the name here?
647: i = 0; // use first one
648: }
649: return i;
650: }
651:
652: /**
653: * List all subfolders matching the specified pattern.
654: */
655: @Override
656: public Folder[] list(String pattern) throws MessagingException {
657: return doList(pattern, false);
658: }
659:
660: /**
661: * List all subscribed subfolders matching the specified pattern.
662: */
663: @Override
664: public Folder[] listSubscribed(String pattern) throws MessagingException {
665: return doList(pattern, true);
666: }
667:
668: private synchronized Folder[] doList(final String pattern,
669: final boolean subscribed) throws MessagingException {
670: checkExists(); // insure that this folder does exist.
671:
672: // Why waste a roundtrip to the server?
673: if (attributes != null && !isDirectory())
674: return new Folder[0];
675:
676: final char c = getSeparator();
677:
678: ListInfo[] li = (ListInfo[]) doCommandIgnoreFailure(
679: new ProtocolCommand() {
680: @Override
681: public Object doCommand(IMAPProtocol p)
682: throws ProtocolException {
683:• if (subscribed)
684: return p.lsub("", fullName + c + pattern);
685: else
686: return p.list("", fullName + c + pattern);
687: }
688: });
689:
690: if (li == null)
691: return new Folder[0];
692:
693: /*
694: * The UW based IMAP4 servers (e.g. SIMS2.0) include
695: * current folder (terminated with the separator), when
696: * the LIST pattern is '%' or '*'. i.e, <LIST "" mail/%>
697: * returns "mail/" as the first LIST response.
698: *
699: * Doesn't make sense to include the current folder in this
700: * case, so we filter it out. Note that I'm assuming that
701: * the offending response is the *first* one, my experiments
702: * with the UW & SIMS2.0 servers indicate that ..
703: */
704: int start = 0;
705: // Check the first LIST response.
706: if (li.length > 0 && li[0].name.equals(fullName + c))
707: start = 1; // start from index = 1
708:
709: IMAPFolder[] folders = new IMAPFolder[li.length - start];
710: IMAPStore st = (IMAPStore) store;
711: for (int i = start; i < li.length; i++)
712: folders[i - start] = st.newIMAPFolder(li[i]);
713: return folders;
714: }
715:
716: /**
717: * Get the separator character.
718: */
719: @Override
720: public synchronized char getSeparator() throws MessagingException {
721: if (separator == UNKNOWN_SEPARATOR) {
722: ListInfo[] li = null;
723:
724: li = (ListInfo[]) doCommand(new ProtocolCommand() {
725: @Override
726: public Object doCommand(IMAPProtocol p)
727: throws ProtocolException {
728: // REV1 allows the following LIST format to obtain
729: // the hierarchy delimiter of non-existent folders
730: if (p.isREV1()) // IMAP4rev1
731: return p.list(fullName, "");
732: else // IMAP4, note that this folder must exist for this
733: // to work :(
734: return p.list("", fullName);
735: }
736: });
737:
738: if (li != null)
739: separator = li[0].separator;
740: else
741: separator = '/'; // punt !
742: }
743: return separator;
744: }
745:
746: /**
747: * Get the type of this folder.
748: */
749: @Override
750: public synchronized int getType() throws MessagingException {
751: if (opened) {
752: // never throw FolderNotFoundException if folder is open
753: if (attributes == null)
754: exists(); // try to fetch attributes
755: } else {
756: checkExists();
757: }
758: return type;
759: }
760:
761: /**
762: * Check whether this folder is subscribed.
763: */
764: @Override
765: public synchronized boolean isSubscribed() {
766: ListInfo[] li = null;
767: final String lname;
768: if (isNamespace && separator != '\0')
769: lname = fullName + separator;
770: else
771: lname = fullName;
772:
773: try {
774: li = (ListInfo[]) doProtocolCommand(new ProtocolCommand() {
775: @Override
776: public Object doCommand(IMAPProtocol p)
777: throws ProtocolException {
778: return p.lsub("", lname);
779: }
780: });
781: } catch (ProtocolException pex) {
782: }
783:
784: if (li != null) {
785: int i = findName(li, lname);
786: return li[i].canOpen;
787: } else
788: return false;
789: }
790:
791: /**
792: * Subscribe/Unsubscribe this folder.
793: */
794: @Override
795: public synchronized void setSubscribed(final boolean subscribe)
796: throws MessagingException {
797: doCommandIgnoreFailure(new ProtocolCommand() {
798: @Override
799: public Object doCommand(IMAPProtocol p) throws ProtocolException {
800: if (subscribe)
801: p.subscribe(fullName);
802: else
803: p.unsubscribe(fullName);
804: return null;
805: }
806: });
807: }
808:
809: /**
810: * Create this folder, with the specified type.
811: */
812: @Override
813: public synchronized boolean create(final int type)
814: throws MessagingException {
815:
816: char c = 0;
817: if ((type & HOLDS_MESSAGES) == 0) // only holds folders
818: c = getSeparator();
819: final char sep = c;
820: Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
821: @Override
822: public Object doCommand(IMAPProtocol p)
823: throws ProtocolException {
824: if ((type & HOLDS_MESSAGES) == 0) // only holds folders
825: p.create(fullName + sep);
826: else {
827: p.create(fullName);
828:
829: // Certain IMAP servers do not allow creation of folders
830: // that can contain messages *and* subfolders. So, if we
831: // were asked to create such a folder, we should verify
832: // that we could indeed do so.
833: if ((type & HOLDS_FOLDERS) != 0) {
834: // we want to hold subfolders and messages. Check
835: // whether we could create such a folder.
836: ListInfo[] li = p.list("", fullName);
837: if (li != null && !li[0].hasInferiors) {
838: // Hmm ..the new folder
839: // doesn't support Inferiors ? Fail
840: p.delete(fullName);
841: throw new ProtocolException("Unsupported type");
842: }
843: }
844: }
845: return Boolean.TRUE;
846: }
847: });
848:
849: if (ret == null)
850: return false; // CREATE failure, maybe this
851: // folder already exists ?
852:
853: // exists = true;
854: // this.type = type;
855: boolean retb = exists(); // set exists, type, and attributes
856: if (retb) // Notify listeners on self and our Store
857: notifyFolderListeners(FolderEvent.CREATED);
858: return retb;
859: }
860:
861: /**
862: * Check whether this folder has new messages.
863: */
864: @Override
865: public synchronized boolean hasNewMessages() throws MessagingException {
866: synchronized (messageCacheLock) {
867: if (opened) { // If we are open, we already have this information
868: // Folder is open, make sure information is up to date
869: // tickle the folder and store connections.
870: try {
871: keepConnectionAlive(true);
872: } catch (ConnectionException cex) {
873: throw new FolderClosedException(this, cex.getMessage());
874: } catch (ProtocolException pex) {
875: throw new MessagingException(pex.getMessage(), pex);
876: }
877: return recent > 0 ? true : false;
878: }
879: }
880:
881: // First, the cheap way - use LIST and look for the \Marked
882: // or \Unmarked tag
883:
884: ListInfo[] li = null;
885: final String lname;
886: if (isNamespace && separator != '\0')
887: lname = fullName + separator;
888: else
889: lname = fullName;
890: li = (ListInfo[]) doCommandIgnoreFailure(new ProtocolCommand() {
891: @Override
892: public Object doCommand(IMAPProtocol p) throws ProtocolException {
893: return p.list("", lname);
894: }
895: });
896:
897: // if folder doesn't exist, throw exception
898: if (li == null)
899: throw new FolderNotFoundException(this, fullName + " not found");
900:
901: int i = findName(li, lname);
902: if (li[i].changeState == ListInfo.CHANGED)
903: return true;
904: else if (li[i].changeState == ListInfo.UNCHANGED)
905: return false;
906:
907: // LIST didn't work. Try the hard way, using STATUS
908: try {
909: Status status = getStatus();
910: if (status.recent > 0)
911: return true;
912: else
913: return false;
914: } catch (BadCommandException bex) {
915: // Probably doesn't support STATUS, tough luck.
916: return false;
917: } catch (ConnectionException cex) {
918: throw new StoreClosedException(store, cex.getMessage());
919: } catch (ProtocolException pex) {
920: throw new MessagingException(pex.getMessage(), pex);
921: }
922: }
923:
924: /**
925: * Get the named subfolder.
926: */
927: @Override
928: public synchronized Folder getFolder(String name)
929: throws MessagingException {
930: // If we know that this folder is *not* a directory, don't
931: // send the request to the server at all ...
932: if (attributes != null && !isDirectory())
933: throw new MessagingException("Cannot contain subfolders");
934:
935: char c = getSeparator();
936: return ((IMAPStore) store).newIMAPFolder(fullName + c + name, c);
937: }
938:
939: /**
940: * Delete this folder.
941: */
942: @Override
943: public synchronized boolean delete(boolean recurse)
944: throws MessagingException {
945: checkClosed(); // insure that this folder is closed.
946:
947: if (recurse) {
948: // Delete all subfolders.
949: Folder[] f = list();
950: for (int i = 0; i < f.length; i++)
951: f[i].delete(recurse); // ignore intermediate failures
952: }
953:
954: // Attempt to delete this folder
955:
956: Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
957: @Override
958: public Object doCommand(IMAPProtocol p) throws ProtocolException {
959: p.delete(fullName);
960: return Boolean.TRUE;
961: }
962: });
963:
964: if (ret == null)
965: // Non-existent folder/No permission ??
966: return false;
967:
968: // DELETE succeeded.
969: exists = false;
970: attributes = null;
971:
972: // Notify listeners on self and our Store
973: notifyFolderListeners(FolderEvent.DELETED);
974: return true;
975: }
976:
977: /**
978: * Rename this folder.
979: */
980: @Override
981: public synchronized boolean renameTo(final Folder f)
982: throws MessagingException {
983: checkClosed(); // insure that we are closed.
984: checkExists();
985: if (f.getStore() != store)
986: throw new MessagingException("Can't rename across Stores");
987:
988:
989: Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
990: @Override
991: public Object doCommand(IMAPProtocol p) throws ProtocolException {
992: p.rename(fullName, f.getFullName());
993: return Boolean.TRUE;
994: }
995: });
996:
997: if (ret == null)
998: return false;
999:
1000: exists = false;
1001: attributes = null;
1002: notifyFolderRenamedListeners(f);
1003: return true;
1004: }
1005:
1006: /**
1007: * Open this folder in the given mode.
1008: */
1009: @Override
1010: public synchronized void open(int mode) throws MessagingException {
1011: open(mode, null);
1012: }
1013:
1014: /**
1015: * Open this folder in the given mode, with the given
1016: * resynchronization data.
1017: *
1018: * @throws MessagingException if the open fails
1019: * @param mode the open mode (Folder.READ_WRITE or Folder.READ_ONLY)
1020: * @param rd the ResyncData instance
1021: * @return a List of MailEvent instances, or null if none
1022: * @since JavaMail 1.5.1
1023: */
1024: public synchronized List<MailEvent> open(int mode, ResyncData rd)
1025: throws MessagingException {
1026: checkClosed(); // insure that we are not already open
1027:
1028: MailboxInfo mi = null;
1029: // Request store for our own protocol connection.
1030: protocol = ((IMAPStore) store).getProtocol(this);
1031:
1032: List<MailEvent> openEvents = null;
1033: synchronized (messageCacheLock) { // Acquire messageCacheLock
1034:
1035: /*
1036: * Add response handler right away so we get any alerts or
1037: * notifications that occur during the SELECT or EXAMINE.
1038: * Have to be sure to remove it if we fail to open the
1039: * folder.
1040: */
1041: protocol.addResponseHandler(this);
1042:
1043: try {
1044: /*
1045: * Enable QRESYNC or CONDSTORE if needed and not enabled.
1046: * QRESYNC implies CONDSTORE, but servers that support
1047: * QRESYNC are not required to support just CONDSTORE
1048: * per RFC 5162.
1049: */
1050: if (rd != null) {
1051: if (rd == ResyncData.CONDSTORE) {
1052: if (!protocol.isEnabled("CONDSTORE") &&
1053: !protocol.isEnabled("QRESYNC")) {
1054: if (protocol.hasCapability("CONDSTORE"))
1055: protocol.enable("CONDSTORE");
1056: else
1057: protocol.enable("QRESYNC");
1058: }
1059: } else {
1060: if (!protocol.isEnabled("QRESYNC"))
1061: protocol.enable("QRESYNC");
1062: }
1063: }
1064:
1065: if (mode == READ_ONLY)
1066: mi = protocol.examine(fullName, rd);
1067: else
1068: mi = protocol.select(fullName, rd);
1069: } catch (CommandFailedException cex) {
1070: /*
1071: * Handle SELECT or EXAMINE failure.
1072: * Try to figure out why the operation failed so we can
1073: * report a more reasonable exception.
1074: *
1075: * Will use our existing protocol object.
1076: */
1077: try {
1078: checkExists(); // throw exception if folder doesn't exist
1079:
1080: if ((type & HOLDS_MESSAGES) == 0)
1081: throw new MessagingException(
1082: "folder cannot contain messages");
1083: throw new MessagingException(cex.getMessage(), cex);
1084:
1085: } finally {
1086: // folder not open, don't keep this information
1087: exists = false;
1088: attributes = null;
1089: type = 0;
1090: // connection still good, return it
1091: releaseProtocol(true);
1092: }
1093: // NOTREACHED
1094: } catch (ProtocolException pex) {
1095: // got a BAD or a BYE; connection may be bad, close it
1096: try {
1097: throw logoutAndThrow(pex.getMessage(), pex);
1098: } finally {
1099: releaseProtocol(false);
1100: }
1101: }
1102:
1103: if (mi.mode != mode) {
1104: if (mode == READ_WRITE && mi.mode == READ_ONLY &&
1105: ((IMAPStore) store).allowReadOnlySelect()) {
1106: ; // all ok, allow it
1107: } else { // otherwise, it's an error
1108: ReadOnlyFolderException ife = new ReadOnlyFolderException(
1109: this, "Cannot open in desired mode");
1110: throw cleanupAndThrow(ife);
1111: }
1112: }
1113:
1114: // Initialize stuff.
1115: opened = true;
1116: reallyClosed = false;
1117: this.mode = mi.mode;
1118: availableFlags = mi.availableFlags;
1119: permanentFlags = mi.permanentFlags;
1120: total = realTotal = mi.total;
1121: recent = mi.recent;
1122: uidvalidity = mi.uidvalidity;
1123: uidnext = mi.uidnext;
1124: uidNotSticky = mi.uidNotSticky;
1125: highestmodseq = mi.highestmodseq;
1126:
1127: // Create the message cache of appropriate size
1128: messageCache = new MessageCache(this, (IMAPStore) store, total);
1129:
1130: // process saved responses and return corresponding events
1131: if (mi.responses != null) {
1132: openEvents = new ArrayList<>();
1133: for (IMAPResponse ir : mi.responses) {
1134: if (ir.keyEquals("VANISHED")) {
1135: // "VANISHED" SP ["(EARLIER)"] SP known-uids
1136: String[] s = ir.readAtomStringList();
1137: // check that it really is "EARLIER"
1138: if (s == null || s.length != 1 ||
1139: !s[0].equalsIgnoreCase("EARLIER"))
1140: continue; // it's not, what to do with it here?
1141: String uids = ir.readAtom();
1142: UIDSet[] uidset = UIDSet.parseUIDSets(uids);
1143: long[] luid = UIDSet.toArray(uidset, uidnext);
1144: if (luid != null && luid.length > 0)
1145: openEvents.add(
1146: new MessageVanishedEvent(this, luid));
1147: } else if (ir.keyEquals("FETCH")) {
1148: assert ir instanceof FetchResponse :
1149: "!ir instanceof FetchResponse";
1150: Message msg = processFetchResponse((FetchResponse) ir);
1151: if (msg != null)
1152: openEvents.add(new MessageChangedEvent(this,
1153: MessageChangedEvent.FLAGS_CHANGED, msg));
1154: }
1155: }
1156: }
1157: } // Release lock
1158:
1159: exists = true; // if we opened it, it must exist
1160: attributes = null; // but we don't yet know its attributes
1161: type = HOLDS_MESSAGES; // lacking more info, we know at least this much
1162:
1163: // notify listeners
1164: notifyConnectionListeners(ConnectionEvent.OPENED);
1165:
1166: return openEvents;
1167: }
1168:
1169: private MessagingException cleanupAndThrow(MessagingException ife) {
1170: try {
1171: try {
1172: // close mailbox and return connection
1173: protocol.close();
1174: releaseProtocol(true);
1175: } catch (ProtocolException pex) {
1176: // something went wrong, close connection
1177: try {
1178: addSuppressed(ife, logoutAndThrow(pex.getMessage(), pex));
1179: } finally {
1180: releaseProtocol(false);
1181: }
1182: }
1183: } catch (Throwable thr) {
1184: addSuppressed(ife, thr);
1185: }
1186: return ife;
1187: }
1188:
1189: private MessagingException logoutAndThrow(String why, ProtocolException t) {
1190: MessagingException ife = new MessagingException(why, t);
1191: try {
1192: protocol.logout();
1193: } catch (Throwable thr) {
1194: addSuppressed(ife, thr);
1195: }
1196: return ife;
1197: }
1198:
1199: private void addSuppressed(Throwable ife, Throwable thr) {
1200: if (isRecoverable(thr)) {
1201: ife.addSuppressed(thr);
1202: } else {
1203: thr.addSuppressed(ife);
1204: if (thr instanceof Error) {
1205: throw (Error) thr;
1206: }
1207: if (thr instanceof RuntimeException) {
1208: throw (RuntimeException) thr;
1209: }
1210: throw new RuntimeException("unexpected exception", thr);
1211: }
1212: }
1213:
1214: private boolean isRecoverable(Throwable t) {
1215: return (t instanceof Exception) || (t instanceof LinkageError);
1216: }
1217:
1218: /**
1219: * Prefetch attributes, based on the given FetchProfile.
1220: */
1221: @Override
1222: public synchronized void fetch(Message[] msgs, FetchProfile fp)
1223: throws MessagingException {
1224: // cache this information in case connection is closed and
1225: // protocol is set to null
1226: boolean isRev1;
1227: FetchItem[] fitems;
1228: synchronized (messageCacheLock) {
1229: checkOpened();
1230: isRev1 = protocol.isREV1();
1231: fitems = protocol.getFetchItems();
1232: }
1233:
1234: StringBuilder command = new StringBuilder();
1235: boolean first = true;
1236: boolean allHeaders = false;
1237:
1238: if (fp.contains(FetchProfile.Item.ENVELOPE)) {
1239: command.append(getEnvelopeCommand());
1240: first = false;
1241: }
1242: if (fp.contains(FetchProfile.Item.FLAGS)) {
1243: command.append(first ? "FLAGS" : " FLAGS");
1244: first = false;
1245: }
1246: if (fp.contains(FetchProfile.Item.CONTENT_INFO)) {
1247: command.append(first ? "BODYSTRUCTURE" : " BODYSTRUCTURE");
1248: first = false;
1249: }
1250: if (fp.contains(UIDFolder.FetchProfileItem.UID)) {
1251: command.append(first ? "UID" : " UID");
1252: first = false;
1253: }
1254: if (fp.contains(IMAPFolder.FetchProfileItem.HEADERS)) {
1255: allHeaders = true;
1256: if (isRev1)
1257: command.append(first ?
1258: "BODY.PEEK[HEADER]" : " BODY.PEEK[HEADER]");
1259: else
1260: command.append(first ? "RFC822.HEADER" : " RFC822.HEADER");
1261: first = false;
1262: }
1263: if (fp.contains(IMAPFolder.FetchProfileItem.MESSAGE)) {
1264: allHeaders = true;
1265: if (isRev1)
1266: command.append(first ? "BODY.PEEK[]" : " BODY.PEEK[]");
1267: else
1268: command.append(first ? "RFC822" : " RFC822");
1269: first = false;
1270: }
1271: if (fp.contains(FetchProfile.Item.SIZE) ||
1272: fp.contains(IMAPFolder.FetchProfileItem.SIZE)) {
1273: command.append(first ? "RFC822.SIZE" : " RFC822.SIZE");
1274: first = false;
1275: }
1276: if (fp.contains(IMAPFolder.FetchProfileItem.INTERNALDATE)) {
1277: command.append(first ? "INTERNALDATE" : " INTERNALDATE");
1278: first = false;
1279: }
1280:
1281: // if we're not fetching all headers, fetch individual headers
1282: String[] hdrs = null;
1283: if (!allHeaders) {
1284: hdrs = fp.getHeaderNames();
1285: if (hdrs.length > 0) {
1286: if (!first)
1287: command.append(" ");
1288: command.append(createHeaderCommand(hdrs, isRev1));
1289: }
1290: }
1291:
1292: /*
1293: * Add any additional extension fetch items.
1294: */
1295: for (int i = 0; i < fitems.length; i++) {
1296: if (fp.contains(fitems[i].getFetchProfileItem())) {
1297: if (command.length() != 0)
1298: command.append(" ");
1299: command.append(fitems[i].getName());
1300: }
1301: }
1302:
1303: Utility.Condition condition =
1304: new IMAPMessage.FetchProfileCondition(fp, fitems);
1305:
1306: // Acquire the Folder's MessageCacheLock.
1307: synchronized (messageCacheLock) {
1308:
1309: // check again to make sure folder is still open
1310: checkOpened();
1311:
1312: // Apply the test, and get the sequence-number set for
1313: // the messages that need to be prefetched.
1314: MessageSet[] msgsets = Utility.toMessageSetSorted(msgs, condition);
1315:
1316: if (msgsets == null)
1317: // We already have what we need.
1318: return;
1319:
1320: Response[] r = null;
1321: // to collect non-FETCH responses & unsolicited FETCH FLAG responses
1322: List<Response> v = new ArrayList<>();
1323: try {
1324: r = getProtocol().fetch(msgsets, command.toString());
1325: } catch (ConnectionException cex) {
1326: throw new FolderClosedException(this, cex.getMessage());
1327: } catch (CommandFailedException cfx) {
1328: // Ignore these, as per RFC 2180
1329: } catch (ProtocolException pex) {
1330: throw new MessagingException(pex.getMessage(), pex);
1331: }
1332:
1333: if (r == null)
1334: return;
1335:
1336: for (int i = 0; i < r.length; i++) {
1337: if (r[i] == null)
1338: continue;
1339: if (!(r[i] instanceof FetchResponse)) {
1340: v.add(r[i]); // Unsolicited Non-FETCH response
1341: continue;
1342: }
1343:
1344: // Got a FetchResponse.
1345: FetchResponse f = (FetchResponse) r[i];
1346: // Get the corresponding message.
1347: IMAPMessage msg = getMessageBySeqNumber(f.getNumber());
1348:
1349: int count = f.getItemCount();
1350: boolean unsolicitedFlags = false;
1351:
1352: for (int j = 0; j < count; j++) {
1353: Item item = f.getItem(j);
1354: // Check for the FLAGS item
1355: if (item instanceof Flags &&
1356: (!fp.contains(FetchProfile.Item.FLAGS) ||
1357: msg == null)) {
1358: // Ok, Unsolicited FLAGS update.
1359: unsolicitedFlags = true;
1360: } else if (msg != null)
1361: msg.handleFetchItem(item, hdrs, allHeaders);
1362: }
1363: if (msg != null)
1364: msg.handleExtensionFetchItems(f.getExtensionItems());
1365:
1366: // If this response contains any unsolicited FLAGS
1367: // add it to the unsolicited response vector
1368: if (unsolicitedFlags)
1369: v.add(f);
1370: }
1371:
1372: // Dispatch any unsolicited responses
1373: if (!v.isEmpty()) {
1374: Response[] responses = new Response[v.size()];
1375: v.toArray(responses);
1376: handleResponses(responses);
1377: }
1378:
1379: } // Release messageCacheLock
1380: }
1381:
1382: /**
1383: * Return the IMAP FETCH items to request in order to load
1384: * all the "envelope" data. Subclasses can override this
1385: * method to fetch more data when FetchProfile.Item.ENVELOPE
1386: * is requested.
1387: *
1388: * @return the IMAP FETCH items to request
1389: * @since JavaMail 1.4.6
1390: */
1391: protected String getEnvelopeCommand() {
1392: return IMAPMessage.EnvelopeCmd;
1393: }
1394:
1395: /**
1396: * Create a new IMAPMessage object to represent the given message number.
1397: * Subclasses of IMAPFolder may override this method to create a
1398: * subclass of IMAPMessage.
1399: *
1400: * @param msgnum the message sequence number
1401: * @return the new IMAPMessage object
1402: * @since JavaMail 1.4.6
1403: */
1404: protected IMAPMessage newIMAPMessage(int msgnum) {
1405: return new IMAPMessage(this, msgnum);
1406: }
1407:
1408: /**
1409: * Create the appropriate IMAP FETCH command items to fetch the
1410: * requested headers.
1411: */
1412: private String createHeaderCommand(String[] hdrs, boolean isRev1) {
1413: StringBuilder sb;
1414:
1415: if (isRev1)
1416: sb = new StringBuilder("BODY.PEEK[HEADER.FIELDS (");
1417: else
1418: sb = new StringBuilder("RFC822.HEADER.LINES (");
1419:
1420: for (int i = 0; i < hdrs.length; i++) {
1421: if (i > 0)
1422: sb.append(" ");
1423: sb.append(hdrs[i]);
1424: }
1425:
1426: if (isRev1)
1427: sb.append(")]");
1428: else
1429: sb.append(")");
1430:
1431: return sb.toString();
1432: }
1433:
1434: /**
1435: * Set the specified flags for the given array of messages.
1436: */
1437: @Override
1438: public synchronized void setFlags(Message[] msgs, Flags flag, boolean value)
1439: throws MessagingException {
1440: checkOpened();
1441: checkFlags(flag); // validate flags
1442:
1443: if (msgs.length == 0) // boundary condition
1444: return;
1445:
1446: synchronized (messageCacheLock) {
1447: try {
1448: IMAPProtocol p = getProtocol();
1449: MessageSet[] ms = Utility.toMessageSetSorted(msgs, null);
1450: if (ms == null)
1451: throw new MessageRemovedException(
1452: "Messages have been removed");
1453: p.storeFlags(ms, flag, value);
1454: } catch (ConnectionException cex) {
1455: throw new FolderClosedException(this, cex.getMessage());
1456: } catch (ProtocolException pex) {
1457: throw new MessagingException(pex.getMessage(), pex);
1458: }
1459: }
1460: }
1461:
1462: /**
1463: * Set the specified flags for the given range of message numbers.
1464: */
1465: @Override
1466: public synchronized void setFlags(int start, int end,
1467: Flags flag, boolean value) throws MessagingException {
1468: checkOpened();
1469: Message[] msgs = new Message[end - start + 1];
1470: int i = 0;
1471: for (int n = start; n <= end; n++)
1472: msgs[i++] = getMessage(n);
1473: setFlags(msgs, flag, value);
1474: }
1475:
1476: /**
1477: * Set the specified flags for the given array of message numbers.
1478: */
1479: @Override
1480: public synchronized void setFlags(int[] msgnums, Flags flag, boolean value)
1481: throws MessagingException {
1482: checkOpened();
1483: Message[] msgs = new Message[msgnums.length];
1484: for (int i = 0; i < msgnums.length; i++)
1485: msgs[i] = getMessage(msgnums[i]);
1486: setFlags(msgs, flag, value);
1487: }
1488:
1489: /**
1490: * Close this folder.
1491: */
1492: @Override
1493: public synchronized void close(boolean expunge) throws MessagingException {
1494: close(expunge, false);
1495: }
1496:
1497: /**
1498: * Close this folder without waiting for the server.
1499: *
1500: * @exception MessagingException for failures
1501: */
1502: public synchronized void forceClose() throws MessagingException {
1503: close(false, true);
1504: }
1505:
1506: /*
1507: * Common close method.
1508: */
1509: private void close(boolean expunge, boolean force)
1510: throws MessagingException {
1511: assert Thread.holdsLock(this);
1512: synchronized (messageCacheLock) {
1513: /*
1514: * If we already know we're closed, this is illegal.
1515: * Can't use checkOpened() because if we were forcibly
1516: * closed asynchronously we just want to complete the
1517: * closing here.
1518: */
1519: if (!opened && reallyClosed)
1520: throw new IllegalStateException(
1521: "This operation is not allowed on a closed folder"
1522: );
1523:
1524: reallyClosed = true; // Ok, lets reset
1525:
1526: // Maybe this folder is already closed, or maybe another
1527: // thread which had the messageCacheLock earlier, found
1528: // that our server connection is dead and cleaned up
1529: // everything ..
1530: if (!opened)
1531: return;
1532:
1533: boolean reuseProtocol = true;
1534: try {
1535: waitIfIdle();
1536: if (force) {
1537: logger.log(Level.FINE, "forcing folder {0} to close",
1538: fullName);
1539: if (protocol != null)
1540: protocol.disconnect();
1541: } else if (((IMAPStore) store).isConnectionPoolFull()) {
1542: // If the connection pool is full, logout the connection
1543: logger.fine(
1544: "pool is full, not adding an Authenticated connection");
1545:
1546: // If the expunge flag is set, close the folder first.
1547: if (expunge && protocol != null)
1548: protocol.close();
1549:
1550: if (protocol != null)
1551: protocol.logout();
1552: } else {
1553: // If the expunge flag is set or we're open read-only we
1554: // can just close the folder, otherwise open it read-only
1555: // before closing, or unselect it if supported.
1556: if (!expunge && mode == READ_WRITE) {
1557: try {
1558: if (protocol != null &&
1559: protocol.hasCapability("UNSELECT"))
1560: protocol.unselect();
1561: else {
1562: // Unselect isn't supported so we need to
1563: // select a folder to cause this one to be
1564: // deselected without expunging messages.
1565: // We try to do that by reopening the current
1566: // folder read-only. If the current folder
1567: // was renamed out from under us, the EXAMINE
1568: // might fail, but that's ok because it still
1569: // leaves us with the folder deselected.
1570: if (protocol != null) {
1571: boolean selected = true;
1572: try {
1573: protocol.examine(fullName);
1574: // success, folder still selected
1575: } catch (CommandFailedException ex) {
1576: // EXAMINE failed, folder is no
1577: // longer selected
1578: selected = false;
1579: }
1580: if (selected && protocol != null)
1581: protocol.close();
1582: }
1583: }
1584: } catch (ProtocolException pex2) {
1585: reuseProtocol = false; // something went wrong
1586: }
1587: } else {
1588: if (protocol != null)
1589: protocol.close();
1590: }
1591: }
1592: } catch (ProtocolException pex) {
1593: throw new MessagingException(pex.getMessage(), pex);
1594: } finally {
1595: // cleanup if we haven't already
1596: if (opened)
1597: cleanup(reuseProtocol);
1598: }
1599: }
1600: }
1601:
1602: // NOTE: this method can currently be invoked from close() or
1603: // from handleResponses(). Both invocations are conditional,
1604: // based on the "opened" flag, so we are sure that multiple
1605: // Connection.CLOSED events are not generated. Also both
1606: // invocations are from within messageCacheLock-ed areas.
1607: private void cleanup(boolean returnToPool) {
1608: assert Thread.holdsLock(messageCacheLock);
1609: releaseProtocol(returnToPool);
1610: messageCache = null;
1611: uidTable = null;
1612: exists = false; // to force a recheck in exists().
1613: attributes = null;
1614: opened = false;
1615: idleState = RUNNING; // just in case
1616: messageCacheLock.notifyAll(); // wake up anyone waiting
1617: notifyConnectionListeners(ConnectionEvent.CLOSED);
1618: }
1619:
1620: /**
1621: * Check whether this connection is really open.
1622: */
1623: @Override
1624: public synchronized boolean isOpen() {
1625: synchronized (messageCacheLock) {
1626: // Probe the connection to make sure its really open.
1627: if (opened) {
1628: try {
1629: keepConnectionAlive(false);
1630: } catch (ProtocolException pex) {
1631: }
1632: }
1633: }
1634:
1635: return opened;
1636: }
1637:
1638: /**
1639: * Return the permanent flags supported by the server.
1640: */
1641: @Override
1642: public synchronized Flags getPermanentFlags() {
1643: if (permanentFlags == null)
1644: return null;
1645: return (Flags) (permanentFlags.clone());
1646: }
1647:
1648: /**
1649: * Get the total message count.
1650: */
1651: @Override
1652: public synchronized int getMessageCount() throws MessagingException {
1653: synchronized (messageCacheLock) {
1654: if (opened) {
1655: // Folder is open, we know what the total message count is ..
1656: // tickle the folder and store connections.
1657: try {
1658: keepConnectionAlive(true);
1659: return total;
1660: } catch (ConnectionException cex) {
1661: throw new FolderClosedException(this, cex.getMessage());
1662: } catch (ProtocolException pex) {
1663: throw new MessagingException(pex.getMessage(), pex);
1664: }
1665: }
1666: }
1667:
1668: // If this folder is not yet open, we use STATUS to
1669: // get the total message count
1670: checkExists();
1671: try {
1672: Status status = getStatus();
1673: return status.total;
1674: } catch (BadCommandException bex) {
1675: // doesn't support STATUS, probably vanilla IMAP4 ..
1676: // lets try EXAMINE
1677: IMAPProtocol p = null;
1678:
1679: try {
1680: p = getStoreProtocol(); // XXX
1681: MailboxInfo minfo = p.examine(fullName);
1682: p.close();
1683: return minfo.total;
1684: } catch (ProtocolException pex) {
1685: // Give up.
1686: throw new MessagingException(pex.getMessage(), pex);
1687: } finally {
1688: releaseStoreProtocol(p);
1689: }
1690: } catch (ConnectionException cex) {
1691: throw new StoreClosedException(store, cex.getMessage());
1692: } catch (ProtocolException pex) {
1693: throw new MessagingException(pex.getMessage(), pex);
1694: }
1695: }
1696:
1697: /**
1698: * Get the new message count.
1699: */
1700: @Override
1701: public synchronized int getNewMessageCount() throws MessagingException {
1702: synchronized (messageCacheLock) {
1703: if (opened) {
1704: // Folder is open, we know what the new message count is ..
1705: // tickle the folder and store connections.
1706: try {
1707: keepConnectionAlive(true);
1708: return recent;
1709: } catch (ConnectionException cex) {
1710: throw new FolderClosedException(this, cex.getMessage());
1711: } catch (ProtocolException pex) {
1712: throw new MessagingException(pex.getMessage(), pex);
1713: }
1714: }
1715: }
1716:
1717: // If this folder is not yet open, we use STATUS to
1718: // get the new message count
1719: checkExists();
1720: try {
1721: Status status = getStatus();
1722: return status.recent;
1723: } catch (BadCommandException bex) {
1724: // doesn't support STATUS, probably vanilla IMAP4 ..
1725: // lets try EXAMINE
1726: IMAPProtocol p = null;
1727:
1728: try {
1729: p = getStoreProtocol(); // XXX
1730: MailboxInfo minfo = p.examine(fullName);
1731: p.close();
1732: return minfo.recent;
1733: } catch (ProtocolException pex) {
1734: // Give up.
1735: throw new MessagingException(pex.getMessage(), pex);
1736: } finally {
1737: releaseStoreProtocol(p);
1738: }
1739: } catch (ConnectionException cex) {
1740: throw new StoreClosedException(store, cex.getMessage());
1741: } catch (ProtocolException pex) {
1742: throw new MessagingException(pex.getMessage(), pex);
1743: }
1744: }
1745:
1746: /**
1747: * Get the unread message count.
1748: */
1749: @Override
1750: public synchronized int getUnreadMessageCount()
1751: throws MessagingException {
1752: if (!opened) {
1753: checkExists();
1754: // If this folder is not yet open, we use STATUS to
1755: // get the unseen message count
1756: try {
1757: Status status = getStatus();
1758: return status.unseen;
1759: } catch (BadCommandException bex) {
1760: // doesn't support STATUS, probably vanilla IMAP4 ..
1761: // Could EXAMINE, SEARCH for UNREAD messages and
1762: // return the count .. bah, not worth it.
1763: return -1;
1764: } catch (ConnectionException cex) {
1765: throw new StoreClosedException(store, cex.getMessage());
1766: } catch (ProtocolException pex) {
1767: throw new MessagingException(pex.getMessage(), pex);
1768: }
1769: }
1770:
1771: // if opened, issue server-side search for messages that do
1772: // *not* have the SEEN flag.
1773: Flags f = new Flags();
1774: f.add(Flags.Flag.SEEN);
1775: try {
1776: synchronized (messageCacheLock) {
1777: int[] matches = getProtocol().search(new FlagTerm(f, false));
1778: return matches.length; // NOTE: 'matches' is never null
1779: }
1780: } catch (ConnectionException cex) {
1781: throw new FolderClosedException(this, cex.getMessage());
1782: } catch (ProtocolException pex) {
1783: // Shouldn't happen
1784: throw new MessagingException(pex.getMessage(), pex);
1785: }
1786: }
1787:
1788: /**
1789: * Get the deleted message count.
1790: */
1791: @Override
1792: public synchronized int getDeletedMessageCount()
1793: throws MessagingException {
1794: if (!opened) {
1795: checkExists();
1796: // no way to do this on closed folders
1797: return -1;
1798: }
1799:
1800: // if opened, issue server-side search for messages that do
1801: // have the DELETED flag.
1802: Flags f = new Flags();
1803: f.add(Flags.Flag.DELETED);
1804: try {
1805: synchronized (messageCacheLock) {
1806: int[] matches = getProtocol().search(new FlagTerm(f, true));
1807: return matches.length; // NOTE: 'matches' is never null
1808: }
1809: } catch (ConnectionException cex) {
1810: throw new FolderClosedException(this, cex.getMessage());
1811: } catch (ProtocolException pex) {
1812: // Shouldn't happen
1813: throw new MessagingException(pex.getMessage(), pex);
1814: }
1815: }
1816:
1817: /*
1818: * Get results of STATUS command for this folder, checking cache first.
1819: * ASSERT: Must be called with this folder's synchronization lock held.
1820: * ASSERT: The folder must be closed.
1821: */
1822: private Status getStatus() throws ProtocolException {
1823: int statusCacheTimeout = ((IMAPStore) store).getStatusCacheTimeout();
1824:
1825: // if allowed to cache and our cache is still valid, return it
1826: if (statusCacheTimeout > 0 && cachedStatus != null &&
1827: System.currentTimeMillis() - cachedStatusTime < statusCacheTimeout)
1828: return cachedStatus;
1829:
1830: IMAPProtocol p = null;
1831:
1832: try {
1833: p = getStoreProtocol(); // XXX
1834: Status s = p.status(fullName, null);
1835: // if allowed to cache, do so
1836: if (statusCacheTimeout > 0) {
1837: cachedStatus = s;
1838: cachedStatusTime = System.currentTimeMillis();
1839: }
1840: return s;
1841: } finally {
1842: releaseStoreProtocol(p);
1843: }
1844: }
1845:
1846: /**
1847: * Get the specified message.
1848: */
1849: @Override
1850: public synchronized Message getMessage(int msgnum)
1851: throws MessagingException {
1852: checkOpened();
1853: checkRange(msgnum);
1854:
1855: return messageCache.getMessage(msgnum);
1856: }
1857:
1858: /**
1859: * {@inheritDoc}
1860: */
1861: @Override
1862: public synchronized Message[] getMessages() throws MessagingException {
1863: /*
1864: * Need to override Folder method to throw FolderClosedException
1865: * instead of IllegalStateException if not really closed.
1866: */
1867: checkOpened();
1868: int total = getMessageCount();
1869: Message[] msgs = new Message[total];
1870: for (int i = 1; i <= total; i++)
1871: msgs[i - 1] = messageCache.getMessage(i);
1872: return msgs;
1873: }
1874:
1875: /**
1876: * Append the given messages into this folder.
1877: */
1878: @Override
1879: public synchronized void appendMessages(Message[] msgs)
1880: throws MessagingException {
1881: checkExists(); // verify that self exists
1882:
1883: // XXX - have to verify that messages are in a different
1884: // store (if any) than target folder, otherwise could
1885: // deadlock trying to fetch messages on the same connection
1886: // we're using for the append.
1887:
1888: int maxsize = ((IMAPStore) store).getAppendBufferSize();
1889:
1890: for (int i = 0; i < msgs.length; i++) {
1891: final Message m = msgs[i];
1892: Date d = m.getReceivedDate(); // retain dates
1893: if (d == null)
1894: d = m.getSentDate();
1895: final Date dd = d;
1896: final Flags f = m.getFlags();
1897:
1898: final MessageLiteral mos;
1899: try {
1900: // if we know the message is too big, don't buffer any of it
1901: mos = new MessageLiteral(m,
1902: m.getSize() > maxsize ? 0 : maxsize);
1903: } catch (IOException ex) {
1904: throw new MessagingException(
1905: "IOException while appending messages", ex);
1906: } catch (MessageRemovedException mrex) {
1907: continue; // just skip this expunged message
1908: }
1909:
1910: doCommand(new ProtocolCommand() {
1911: @Override
1912: public Object doCommand(IMAPProtocol p)
1913: throws ProtocolException {
1914: p.append(fullName, f, dd, mos);
1915: return null;
1916: }
1917: });
1918: }
1919: }
1920:
1921: /**
1922: * Append the given messages into this folder.
1923: * Return array of AppendUID objects containing
1924: * UIDs of these messages in the destination folder.
1925: * Each element of the returned array corresponds to
1926: * an element of the <code>msgs</code> array. A null
1927: * element means the server didn't return UID information
1928: * for the appended message. <p>
1929: *
1930: * Depends on the APPENDUID response code defined by the
1931: * UIDPLUS extension -
1932: * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
1933: *
1934: * @param msgs the messages to append
1935: * @return array of AppendUID objects
1936: * @exception MessagingException for failures
1937: * @since JavaMail 1.4
1938: */
1939: public synchronized AppendUID[] appendUIDMessages(Message[] msgs)
1940: throws MessagingException {
1941: checkExists(); // verify that self exists
1942:
1943: // XXX - have to verify that messages are in a different
1944: // store (if any) than target folder, otherwise could
1945: // deadlock trying to fetch messages on the same connection
1946: // we're using for the append.
1947:
1948: int maxsize = ((IMAPStore) store).getAppendBufferSize();
1949:
1950: AppendUID[] uids = new AppendUID[msgs.length];
1951: for (int i = 0; i < msgs.length; i++) {
1952: final Message m = msgs[i];
1953: final MessageLiteral mos;
1954:
1955: try {
1956: // if we know the message is too big, don't buffer any of it
1957: mos = new MessageLiteral(m,
1958: m.getSize() > maxsize ? 0 : maxsize);
1959: } catch (IOException ex) {
1960: throw new MessagingException(
1961: "IOException while appending messages", ex);
1962: } catch (MessageRemovedException mrex) {
1963: continue; // just skip this expunged message
1964: }
1965:
1966: Date d = m.getReceivedDate(); // retain dates
1967: if (d == null)
1968: d = m.getSentDate();
1969: final Date dd = d;
1970: final Flags f = m.getFlags();
1971: AppendUID auid = (AppendUID) doCommand(new ProtocolCommand() {
1972: @Override
1973: public Object doCommand(IMAPProtocol p)
1974: throws ProtocolException {
1975: return p.appenduid(fullName, f, dd, mos);
1976: }
1977: });
1978: uids[i] = auid;
1979: }
1980: return uids;
1981: }
1982:
1983: /**
1984: * Append the given messages into this folder.
1985: * Return array of Message objects representing
1986: * the messages in the destination folder. Note
1987: * that the folder must be open.
1988: * Each element of the returned array corresponds to
1989: * an element of the <code>msgs</code> array. A null
1990: * element means the server didn't return UID information
1991: * for the appended message. <p>
1992: *
1993: * Depends on the APPENDUID response code defined by the
1994: * UIDPLUS extension -
1995: * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
1996: *
1997: * @param msgs the messages to add
1998: * @return the messages in this folder
1999: * @exception MessagingException for failures
2000: * @since JavaMail 1.4
2001: */
2002: public synchronized Message[] addMessages(Message[] msgs)
2003: throws MessagingException {
2004: checkOpened();
2005: Message[] rmsgs = new MimeMessage[msgs.length];
2006: AppendUID[] uids = appendUIDMessages(msgs);
2007: for (int i = 0; i < uids.length; i++) {
2008: AppendUID auid = uids[i];
2009: if (auid != null) {
2010: if (auid.uidvalidity == uidvalidity) {
2011: try {
2012: rmsgs[i] = getMessageByUID(auid.uid);
2013: } catch (MessagingException mex) {
2014: // ignore errors at this stage
2015: }
2016: }
2017: }
2018: }
2019: return rmsgs;
2020: }
2021:
2022: /**
2023: * Copy the specified messages from this folder, to the
2024: * specified destination.
2025: */
2026: @Override
2027: public synchronized void copyMessages(Message[] msgs, Folder folder)
2028: throws MessagingException {
2029: copymoveMessages(msgs, folder, false);
2030: }
2031:
2032: /**
2033: * Copy the specified messages from this folder, to the
2034: * specified destination.
2035: * Return array of AppendUID objects containing
2036: * UIDs of these messages in the destination folder.
2037: * Each element of the returned array corresponds to
2038: * an element of the <code>msgs</code> array. A null
2039: * element means the server didn't return UID information
2040: * for the copied message. <p>
2041: *
2042: * Depends on the COPYUID response code defined by the
2043: * UIDPLUS extension -
2044: * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
2045: *
2046: * @param msgs the messages to copy
2047: * @param folder the folder to copy the messages to
2048: * @return array of AppendUID objects
2049: * @exception MessagingException for failures
2050: * @since JavaMail 1.5.1
2051: */
2052: public synchronized AppendUID[] copyUIDMessages(Message[] msgs,
2053: Folder folder) throws MessagingException {
2054: return copymoveUIDMessages(msgs, folder, false);
2055: }
2056:
2057: /**
2058: * Move the specified messages from this folder, to the
2059: * specified destination.
2060: *
2061: * Depends on the MOVE extension
2062: * (<A HREF="http://www.ietf.org/rfc/rfc6851.txt">RFC 6851</A>).
2063: *
2064: * @param msgs the messages to move
2065: * @param folder the folder to move the messages to
2066: * @exception MessagingException for failures
2067: * @since JavaMail 1.5.4
2068: */
2069: public synchronized void moveMessages(Message[] msgs, Folder folder)
2070: throws MessagingException {
2071: copymoveMessages(msgs, folder, true);
2072: }
2073:
2074: /**
2075: * Move the specified messages from this folder, to the
2076: * specified destination.
2077: * Return array of AppendUID objects containing
2078: * UIDs of these messages in the destination folder.
2079: * Each element of the returned array corresponds to
2080: * an element of the <code>msgs</code> array. A null
2081: * element means the server didn't return UID information
2082: * for the moved message. <p>
2083: *
2084: * Depends on the MOVE extension
2085: * (<A HREF="http://www.ietf.org/rfc/rfc6851.txt">RFC 6851</A>)
2086: * and the COPYUID response code defined by the
2087: * UIDPLUS extension
2088: * (<A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>).
2089: *
2090: * @param msgs the messages to move
2091: * @param folder the folder to move the messages to
2092: * @return array of AppendUID objects
2093: * @exception MessagingException for failures
2094: * @since JavaMail 1.5.4
2095: */
2096: public synchronized AppendUID[] moveUIDMessages(Message[] msgs,
2097: Folder folder) throws MessagingException {
2098: return copymoveUIDMessages(msgs, folder, true);
2099: }
2100:
2101: /**
2102: * Copy or move the specified messages from this folder, to the
2103: * specified destination.
2104: *
2105: * @since JavaMail 1.5.4
2106: */
2107: private synchronized void copymoveMessages(Message[] msgs, Folder folder,
2108: boolean move) throws MessagingException {
2109: checkOpened();
2110:
2111: if (msgs.length == 0) // boundary condition
2112: return;
2113:
2114: // If the destination belongs to our same store, optimize
2115: if (folder.getStore() == store) {
2116: synchronized (messageCacheLock) {
2117: try {
2118: IMAPProtocol p = getProtocol();
2119: MessageSet[] ms = Utility.toMessageSet(msgs, null);
2120: if (ms == null)
2121: throw new MessageRemovedException(
2122: "Messages have been removed");
2123: if (move)
2124: p.move(ms, folder.getFullName());
2125: else
2126: p.copy(ms, folder.getFullName());
2127: } catch (CommandFailedException cfx) {
2128: if (cfx.getMessage().contains("TRYCREATE"))
2129: throw new FolderNotFoundException(
2130: folder,
2131: folder.getFullName() + " does not exist"
2132: );
2133: else
2134: throw new MessagingException(cfx.getMessage(), cfx);
2135: } catch (ConnectionException cex) {
2136: throw new FolderClosedException(this, cex.getMessage());
2137: } catch (ProtocolException pex) {
2138: throw new MessagingException(pex.getMessage(), pex);
2139: }
2140: }
2141: } else // destination is a different store.
2142: if (move)
2143: throw new MessagingException(
2144: "Move between stores not supported");
2145: else
2146: super.copyMessages(msgs, folder);
2147: }
2148:
2149: /**
2150: * Copy or move the specified messages from this folder, to the
2151: * specified destination.
2152: * Return array of AppendUID objects containing
2153: * UIDs of these messages in the destination folder.
2154: * Each element of the returned array corresponds to
2155: * an element of the <code>msgs</code> array. A null
2156: * element means the server didn't return UID information
2157: * for the copied message. <p>
2158: *
2159: * Depends on the COPYUID response code defined by the
2160: * UIDPLUS extension -
2161: * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
2162: * Move depends on the MOVE extension -
2163: * <A HREF="http://www.ietf.org/rfc/rfc6851.txt">RFC 6851</A>.
2164: *
2165: * @param msgs the messages to copy
2166: * @param folder the folder to copy the messages to
2167: * @param move move instead of copy?
2168: * @return array of AppendUID objects
2169: * @exception MessagingException for failures
2170: * @since JavaMail 1.5.4
2171: */
2172: private synchronized AppendUID[] copymoveUIDMessages(Message[] msgs,
2173: Folder folder, boolean move) throws MessagingException {
2174: checkOpened();
2175:
2176: if (msgs.length == 0) // boundary condition
2177: return null;
2178:
2179: // the destination must belong to our same store
2180: if (folder.getStore() != store) // destination is a different store.
2181: throw new MessagingException(
2182: move ?
2183: "can't moveUIDMessages to a different store" :
2184: "can't copyUIDMessages to a different store");
2185:
2186: // call fetch to make sure we have all the UIDs
2187: // necessary to interpret the COPYUID response
2188: FetchProfile fp = new FetchProfile();
2189: fp.add(UIDFolder.FetchProfileItem.UID);
2190: fetch(msgs, fp);
2191: // XXX - could pipeline the FETCH with the COPY/MOVE below
2192:
2193: synchronized (messageCacheLock) {
2194: try {
2195: IMAPProtocol p = getProtocol();
2196: // XXX - messages have to be from this Folder, who checks?
2197: MessageSet[] ms = Utility.toMessageSet(msgs, null);
2198: if (ms == null)
2199: throw new MessageRemovedException(
2200: "Messages have been removed");
2201: CopyUID cuid;
2202: if (move)
2203: cuid = p.moveuid(ms, folder.getFullName());
2204: else
2205: cuid = p.copyuid(ms, folder.getFullName());
2206:
2207: /*
2208: * Correlate source UIDs with destination UIDs.
2209: * This won't be time or space efficient if there's
2210: * a lot of messages.
2211: *
2212: * In order to make sense of the returned UIDs, we need
2213: * the UIDs for every one of the original messages.
2214: * We fetch them above, to make sure we have them.
2215: * This is critical for MOVE since after the MOVE the
2216: * messages are gone/expunged.
2217: *
2218: * Assume the common case is that the messages are
2219: * in order by UID. Map the returned source
2220: * UIDs to their corresponding Message objects.
2221: * Step through the msgs array looking for the
2222: * Message object in the returned source message
2223: * list. Most commonly the source message (UID)
2224: * for the Nth original message will be in the Nth
2225: * position in the returned source message (UID)
2226: * list. Thus, the destination UID is in the Nth
2227: * position in the returned destination UID list.
2228: * But if the source message isn't where expected,
2229: * we have to search the entire source message
2230: * list, starting from where we expect it and
2231: * wrapping around until we've searched it all.
2232: * (Gmail will often return the lists in an unexpected order.)
2233: *
2234: * A possible optimization:
2235: * If the number of UIDs returned is the same as the
2236: * number of messages being copied/moved, we could
2237: * sort the source messages by message number, sort
2238: * the source and destination parallel arrays by source
2239: * UID, and the resulting message and destination UID
2240: * arrays will correspond.
2241: *
2242: * If the returned UID array size is different, some
2243: * message was expunged while we were trying to copy/move it.
2244: * This should be rare but would mean falling back to the
2245: * general algorithm.
2246: */
2247: long[] srcuids = UIDSet.toArray(cuid.src);
2248: long[] dstuids = UIDSet.toArray(cuid.dst);
2249: // map source UIDs to Message objects
2250: // XXX - could inline/optimize this
2251: Message[] srcmsgs = getMessagesByUID(srcuids);
2252: AppendUID[] result = new AppendUID[msgs.length];
2253: for (int i = 0; i < msgs.length; i++) {
2254: int j = i;
2255: do {
2256: if (msgs[i] == srcmsgs[j]) {
2257: result[i] = new AppendUID(
2258: cuid.uidvalidity, dstuids[j]);
2259: break;
2260: }
2261: j++;
2262: if (j >= srcmsgs.length)
2263: j = 0;
2264: } while (j != i);
2265: }
2266: return result;
2267: } catch (CommandFailedException cfx) {
2268: if (cfx.getMessage().contains("TRYCREATE"))
2269: throw new FolderNotFoundException(
2270: folder,
2271: folder.getFullName() + " does not exist"
2272: );
2273: else
2274: throw new MessagingException(cfx.getMessage(), cfx);
2275: } catch (ConnectionException cex) {
2276: throw new FolderClosedException(this, cex.getMessage());
2277: } catch (ProtocolException pex) {
2278: throw new MessagingException(pex.getMessage(), pex);
2279: }
2280: }
2281: }
2282:
2283: /**
2284: * Expunge all messages marked as DELETED.
2285: */
2286: @Override
2287: public synchronized Message[] expunge() throws MessagingException {
2288: return expunge(null);
2289: }
2290:
2291: /**
2292: * Expunge the indicated messages, which must have been marked as DELETED.
2293: *
2294: * Depends on the UIDPLUS extension -
2295: * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
2296: *
2297: * @param msgs the messages to expunge
2298: * @return the expunged messages
2299: * @exception MessagingException for failures
2300: */
2301: public synchronized Message[] expunge(Message[] msgs)
2302: throws MessagingException {
2303: checkOpened();
2304:
2305: if (msgs != null) {
2306: // call fetch to make sure we have all the UIDs
2307: FetchProfile fp = new FetchProfile();
2308: fp.add(UIDFolder.FetchProfileItem.UID);
2309: fetch(msgs, fp);
2310: }
2311:
2312: IMAPMessage[] rmsgs;
2313: synchronized (messageCacheLock) {
2314: doExpungeNotification = false; // We do this ourselves later
2315: try {
2316: IMAPProtocol p = getProtocol();
2317: if (msgs != null)
2318: p.uidexpunge(Utility.toUIDSet(msgs));
2319: else
2320: p.expunge();
2321: } catch (CommandFailedException cfx) {
2322: // expunge not allowed, perhaps due to a permission problem?
2323: if (mode != READ_WRITE)
2324: throw new IllegalStateException(
2325: "Cannot expunge READ_ONLY folder: " + fullName);
2326: else
2327: throw new MessagingException(cfx.getMessage(), cfx);
2328: } catch (ConnectionException cex) {
2329: throw new FolderClosedException(this, cex.getMessage());
2330: } catch (ProtocolException pex) {
2331: // Bad bad server ..
2332: throw new MessagingException(pex.getMessage(), pex);
2333: } finally {
2334: doExpungeNotification = true;
2335: }
2336:
2337: // Cleanup expunged messages and sync messageCache with reality.
2338: if (msgs != null)
2339: rmsgs = messageCache.removeExpungedMessages(msgs);
2340: else
2341: rmsgs = messageCache.removeExpungedMessages();
2342: if (uidTable != null) {
2343: for (int i = 0; i < rmsgs.length; i++) {
2344: IMAPMessage m = rmsgs[i];
2345: /* remove this message from the UIDTable */
2346: long uid = m.getUID();
2347: if (uid != -1)
2348: uidTable.remove(Long.valueOf(uid));
2349: }
2350: }
2351:
2352: // Update 'total'
2353: total = messageCache.size();
2354: }
2355:
2356: // Notify listeners. This time its for real, guys.
2357: if (rmsgs.length > 0)
2358: notifyMessageRemovedListeners(true, rmsgs);
2359: return rmsgs;
2360: }
2361:
2362: /**
2363: * Search whole folder for messages matching the given term.
2364: * If the property <code>mail.imap.throwsearchexception</code> is true,
2365: * and the search term is too complex for the IMAP protocol,
2366: * SearchException is thrown. Otherwise, if the search term is too
2367: * complex, <code>super.search</code> is called to do the search on
2368: * the client.
2369: *
2370: * @param term the search term
2371: * @return the messages that match
2372: * @exception SearchException if mail.imap.throwsearchexception is
2373: * true and the search is too complex for the IMAP protocol
2374: * @exception MessagingException for other failures
2375: */
2376: @Override
2377: public synchronized Message[] search(SearchTerm term)
2378: throws MessagingException {
2379: checkOpened();
2380:
2381: try {
2382: Message[] matchMsgs = null;
2383:
2384: synchronized (messageCacheLock) {
2385: int[] matches = getProtocol().search(term);
2386: if (matches != null)
2387: matchMsgs = getMessagesBySeqNumbers(matches);
2388: }
2389: return matchMsgs;
2390:
2391: } catch (CommandFailedException cfx) {
2392: // unsupported charset or search criterion
2393: return super.search(term);
2394: } catch (SearchException sex) {
2395: // too complex for IMAP
2396: if (((IMAPStore) store).throwSearchException())
2397: throw sex;
2398: return super.search(term);
2399: } catch (ConnectionException cex) {
2400: throw new FolderClosedException(this, cex.getMessage());
2401: } catch (ProtocolException pex) {
2402: // bug in our IMAP layer ?
2403: throw new MessagingException(pex.getMessage(), pex);
2404: }
2405: }
2406:
2407: /**
2408: * Search the folder for messages matching the given term. Returns
2409: * array of matching messages. Returns an empty array if no matching
2410: * messages are found.
2411: */
2412: @Override
2413: public synchronized Message[] search(SearchTerm term, Message[] msgs)
2414: throws MessagingException {
2415: checkOpened();
2416:
2417: if (msgs.length == 0)
2418: // need to return an empty array (not null!)
2419: return msgs;
2420:
2421: try {
2422: Message[] matchMsgs = null;
2423:
2424: synchronized (messageCacheLock) {
2425: IMAPProtocol p = getProtocol();
2426: MessageSet[] ms = Utility.toMessageSetSorted(msgs, null);
2427: if (ms == null)
2428: throw new MessageRemovedException(
2429: "Messages have been removed");
2430: int[] matches = p.search(ms, term);
2431: if (matches != null)
2432: matchMsgs = getMessagesBySeqNumbers(matches);
2433: }
2434: return matchMsgs;
2435:
2436: } catch (CommandFailedException cfx) {
2437: // unsupported charset or search criterion
2438: return super.search(term, msgs);
2439: } catch (SearchException sex) {
2440: // too complex for IMAP
2441: return super.search(term, msgs);
2442: } catch (ConnectionException cex) {
2443: throw new FolderClosedException(this, cex.getMessage());
2444: } catch (ProtocolException pex) {
2445: // bug in our IMAP layer ?
2446: throw new MessagingException(pex.getMessage(), pex);
2447: }
2448: }
2449:
2450: /**
2451: * Sort the messages in the folder according to the sort criteria.
2452: * The messages are returned in the sorted order, but the order of
2453: * the messages in the folder is not changed. <p>
2454: *
2455: * Depends on the SORT extension -
2456: * <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A>.
2457: *
2458: * @param term the SortTerms
2459: * @return the messages in sorted order
2460: * @exception MessagingException for failures
2461: * @since JavaMail 1.4.4
2462: */
2463: public synchronized Message[] getSortedMessages(SortTerm[] term)
2464: throws MessagingException {
2465: return getSortedMessages(term, null);
2466: }
2467:
2468: /**
2469: * Sort the messages in the folder according to the sort criteria.
2470: * The messages are returned in the sorted order, but the order of
2471: * the messages in the folder is not changed. Only messages matching
2472: * the search criteria are considered. <p>
2473: *
2474: * Depends on the SORT extension -
2475: * <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A>.
2476: *
2477: * @param term the SortTerms
2478: * @param sterm the SearchTerm
2479: * @return the messages in sorted order
2480: * @exception MessagingException for failures
2481: * @since JavaMail 1.4.4
2482: */
2483: public synchronized Message[] getSortedMessages(SortTerm[] term,
2484: SearchTerm sterm) throws MessagingException {
2485: checkOpened();
2486:
2487: try {
2488: Message[] matchMsgs = null;
2489:
2490: synchronized (messageCacheLock) {
2491: int[] matches = getProtocol().sort(term, sterm);
2492: if (matches != null)
2493: matchMsgs = getMessagesBySeqNumbers(matches);
2494: }
2495: return matchMsgs;
2496:
2497: } catch (CommandFailedException cfx) {
2498: // unsupported charset or search criterion
2499: throw new MessagingException(cfx.getMessage(), cfx);
2500: } catch (SearchException sex) {
2501: // too complex for IMAP
2502: throw new MessagingException(sex.getMessage(), sex);
2503: } catch (ConnectionException cex) {
2504: throw new FolderClosedException(this, cex.getMessage());
2505: } catch (ProtocolException pex) {
2506: // bug in our IMAP layer ?
2507: throw new MessagingException(pex.getMessage(), pex);
2508: }
2509: }
2510:
2511: /*
2512: * Override Folder method to keep track of whether we have any
2513: * message count listeners. Normally we won't have any, so we
2514: * can avoid creating message objects to pass to the notify
2515: * method. It's too hard to keep track of when all listeners
2516: * are removed, and that's a rare case, so we don't try.
2517: */
2518: @Override
2519: public synchronized void addMessageCountListener(MessageCountListener l) {
2520: super.addMessageCountListener(l);
2521: hasMessageCountListener = true;
2522: }
2523:
2524: /***********************************************************
2525: *                UIDFolder interface methods
2526: **********************************************************/
2527:
2528: /**
2529: * Returns the UIDValidity for this folder.
2530: */
2531: @Override
2532: public synchronized long getUIDValidity() throws MessagingException {
2533: if (opened) // we already have this information
2534: return uidvalidity;
2535:
2536: IMAPProtocol p = null;
2537: Status status = null;
2538:
2539: try {
2540: p = getStoreProtocol(); // XXX
2541: String[] item = {"UIDVALIDITY"};
2542: status = p.status(fullName, item);
2543: } catch (BadCommandException bex) {
2544: // Probably a RFC1730 server
2545: throw new MessagingException("Cannot obtain UIDValidity", bex);
2546: } catch (ConnectionException cex) {
2547: // Oops, the store or folder died on us.
2548: throwClosedException(cex);
2549: } catch (ProtocolException pex) {
2550: throw new MessagingException(pex.getMessage(), pex);
2551: } finally {
2552: releaseStoreProtocol(p);
2553: }
2554:
2555: if (status == null)
2556: throw new MessagingException("Cannot obtain UIDValidity");
2557: return status.uidvalidity;
2558: }
2559:
2560: /**
2561: * Returns the predicted UID that will be assigned to the
2562: * next message that is appended to this folder.
2563: * If the folder is closed, the STATUS command is used to
2564: * retrieve this value. If the folder is open, the value
2565: * returned from the SELECT or EXAMINE command is returned.
2566: * Note that messages may have been appended to the folder
2567: * while it was open and thus this value may be out of
2568: * date. <p>
2569: *
2570: * Servers implementing RFC2060 likely won't return this value
2571: * when a folder is opened. Servers implementing RFC3501
2572: * should return this value when a folder is opened. <p>
2573: *
2574: * @return the UIDNEXT value, or -1 if unknown
2575: * @exception MessagingException for failures
2576: * @since JavaMail 1.3.3
2577: */
2578: @Override
2579: public synchronized long getUIDNext() throws MessagingException {
2580: if (opened) // we already have this information
2581: return uidnext;
2582:
2583: IMAPProtocol p = null;
2584: Status status = null;
2585:
2586: try {
2587: p = getStoreProtocol(); // XXX
2588: String[] item = {"UIDNEXT"};
2589: status = p.status(fullName, item);
2590: } catch (BadCommandException bex) {
2591: // Probably a RFC1730 server
2592: throw new MessagingException("Cannot obtain UIDNext", bex);
2593: } catch (ConnectionException cex) {
2594: // Oops, the store or folder died on us.
2595: throwClosedException(cex);
2596: } catch (ProtocolException pex) {
2597: throw new MessagingException(pex.getMessage(), pex);
2598: } finally {
2599: releaseStoreProtocol(p);
2600: }
2601:
2602: if (status == null)
2603: throw new MessagingException("Cannot obtain UIDNext");
2604: return status.uidnext;
2605: }
2606:
2607: /**
2608: * Get the Message corresponding to the given UID.
2609: * If no such message exists, <code> null </code> is returned.
2610: */
2611: @Override
2612: public synchronized Message getMessageByUID(long uid)
2613: throws MessagingException {
2614: checkOpened(); // insure folder is open
2615:
2616: IMAPMessage m = null;
2617:
2618: try {
2619: synchronized (messageCacheLock) {
2620: Long l = Long.valueOf(uid);
2621:
2622: if (uidTable != null) {
2623: // Check in uidTable
2624: m = uidTable.get(l);
2625: if (m != null) // found it
2626: return m;
2627: } else
2628: uidTable = new Hashtable<>();
2629:
2630: // Check with the server
2631: // Issue UID FETCH command
2632: getProtocol().fetchSequenceNumber(uid);
2633:
2634: if (uidTable != null) {
2635: // Check in uidTable
2636: m = uidTable.get(l);
2637: if (m != null) // found it
2638: return m;
2639: }
2640: }
2641: } catch (ConnectionException cex) {
2642: throw new FolderClosedException(this, cex.getMessage());
2643: } catch (ProtocolException pex) {
2644: throw new MessagingException(pex.getMessage(), pex);
2645: }
2646:
2647: return m;
2648: }
2649:
2650: /**
2651: * Get the Messages specified by the given range. <p>
2652: * Returns Message objects for all valid messages in this range.
2653: * Returns an empty array if no messages are found.
2654: */
2655: @Override
2656: public synchronized Message[] getMessagesByUID(long start, long end)
2657: throws MessagingException {
2658: checkOpened(); // insure that folder is open
2659:
2660: Message[] msgs; // array of messages to be returned
2661:
2662: try {
2663: synchronized (messageCacheLock) {
2664: if (uidTable == null)
2665: uidTable = new Hashtable<>();
2666:
2667: // Issue UID FETCH for given range
2668: long[] ua = getProtocol().fetchSequenceNumbers(start, end);
2669:
2670: List<Message> ma = new ArrayList<>();
2671: // NOTE: Below must be within messageCacheLock region
2672: for (int i = 0; i < ua.length; i++) {
2673: Message m = uidTable.get(Long.valueOf(ua[i]));
2674: if (m != null) // found it
2675: ma.add(m);
2676: }
2677: msgs = ma.toArray(new Message[0]);
2678: }
2679: } catch (ConnectionException cex) {
2680: throw new FolderClosedException(this, cex.getMessage());
2681: } catch (ProtocolException pex) {
2682: throw new MessagingException(pex.getMessage(), pex);
2683: }
2684:
2685: return msgs;
2686: }
2687:
2688: /**
2689: * Get the Messages specified by the given array. <p>
2690: *
2691: * <code>uids.length()</code> elements are returned.
2692: * If any UID in the array is invalid, a <code>null</code> entry
2693: * is returned for that element.
2694: */
2695: @Override
2696: public synchronized Message[] getMessagesByUID(long[] uids)
2697: throws MessagingException {
2698: checkOpened(); // insure that folder is open
2699:
2700: try {
2701: synchronized (messageCacheLock) {
2702: long[] unavailUids = uids;
2703: if (uidTable != null) {
2704: // to collect unavailable UIDs
2705: List<Long> v = new ArrayList<>();
2706: for (long uid : uids) {
2707: if (!uidTable.containsKey(uid)) {
2708: // This UID has not been loaded yet.
2709: v.add(uid);
2710: }
2711: }
2712:
2713: int vsize = v.size();
2714: unavailUids = new long[vsize];
2715: for (int i = 0; i < vsize; i++) {
2716: unavailUids[i] = v.get(i);
2717: }
2718: } else
2719: uidTable = new Hashtable<>();
2720:
2721: if (unavailUids.length > 0) {
2722: // Issue UID FETCH request for given uids
2723: getProtocol().fetchSequenceNumbers(unavailUids);
2724: }
2725:
2726: // Return array of size = uids.length
2727: Message[] msgs = new Message[uids.length];
2728: for (int i = 0; i < uids.length; i++)
2729: msgs[i] = (Message) uidTable.get(Long.valueOf(uids[i]));
2730: return msgs;
2731: }
2732: } catch (ConnectionException cex) {
2733: throw new FolderClosedException(this, cex.getMessage());
2734: } catch (ProtocolException pex) {
2735: throw new MessagingException(pex.getMessage(), pex);
2736: }
2737: }
2738:
2739: /**
2740: * Get the UID for the specified message.
2741: */
2742: @Override
2743: public synchronized long getUID(Message message)
2744: throws MessagingException {
2745: if (message.getFolder() != this)
2746: throw new NoSuchElementException(
2747: "Message does not belong to this folder");
2748:
2749: checkOpened(); // insure that folder is open
2750:
2751: if (!(message instanceof IMAPMessage))
2752: throw new MessagingException("message is not an IMAPMessage");
2753: IMAPMessage m = (IMAPMessage) message;
2754: // If the message already knows its UID, great ..
2755: long uid;
2756: if ((uid = m.getUID()) != -1)
2757: return uid;
2758:
2759: synchronized (messageCacheLock) { // Acquire Lock
2760: try {
2761: IMAPProtocol p = getProtocol();
2762: m.checkExpunged(); // insure that message is not expunged
2763: UID u = p.fetchUID(m.getSequenceNumber());
2764:
2765: if (u != null) {
2766: uid = u.uid;
2767: m.setUID(uid); // set message's UID
2768:
2769: // insert this message into uidTable
2770: if (uidTable == null)
2771: uidTable = new Hashtable<>();
2772: uidTable.put(Long.valueOf(uid), m);
2773: }
2774: } catch (ConnectionException cex) {
2775: throw new FolderClosedException(this, cex.getMessage());
2776: } catch (ProtocolException pex) {
2777: throw new MessagingException(pex.getMessage(), pex);
2778: }
2779: }
2780:
2781: return uid;
2782: }
2783:
2784: /**
2785: * Servers that support the UIDPLUS extension
2786: * (<A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>)
2787: * may indicate that this folder does not support persistent UIDs;
2788: * that is, UIDVALIDITY will be different each time the folder is
2789: * opened. Only valid when the folder is open.
2790: *
2791: * @return true if UIDs are not sticky
2792: * @exception MessagingException for failures
2793: * @exception IllegalStateException if the folder isn't open
2794: * @since JavaMail 1.6.0
2795: * @see "RFC 4315"
2796: */
2797: public synchronized boolean getUIDNotSticky() throws MessagingException {
2798: checkOpened();
2799: return uidNotSticky;
2800: }
2801:
2802: /**
2803: * Get or create Message objects for the UIDs.
2804: */
2805: private Message[] createMessagesForUIDs(long[] uids) {
2806: IMAPMessage[] msgs = new IMAPMessage[uids.length];
2807: for (int i = 0; i < uids.length; i++) {
2808: IMAPMessage m = null;
2809: if (uidTable != null)
2810: m = uidTable.get(Long.valueOf(uids[i]));
2811: if (m == null) {
2812: // fake it, we don't know what message this really is
2813: m = newIMAPMessage(-1); // no sequence number
2814: m.setUID(uids[i]);
2815: m.setExpunged(true);
2816: }
2817: msgs[i++] = m;
2818: }
2819: return msgs;
2820: }
2821:
2822: /**
2823: * Returns the HIGHESTMODSEQ for this folder.
2824: *
2825: * @return the HIGHESTMODSEQ value
2826: * @exception MessagingException for failures
2827: * @since JavaMail 1.5.1
2828: * @see "RFC 4551"
2829: */
2830: public synchronized long getHighestModSeq() throws MessagingException {
2831: if (opened) // we already have this information
2832: return highestmodseq;
2833:
2834: IMAPProtocol p = null;
2835: Status status = null;
2836:
2837: try {
2838: p = getStoreProtocol(); // XXX
2839: if (!p.hasCapability("CONDSTORE"))
2840: throw new BadCommandException("CONDSTORE not supported");
2841: String[] item = {"HIGHESTMODSEQ"};
2842: status = p.status(fullName, item);
2843: } catch (BadCommandException bex) {
2844: // Probably a RFC1730 server
2845: throw new MessagingException("Cannot obtain HIGHESTMODSEQ", bex);
2846: } catch (ConnectionException cex) {
2847: // Oops, the store or folder died on us.
2848: throwClosedException(cex);
2849: } catch (ProtocolException pex) {
2850: throw new MessagingException(pex.getMessage(), pex);
2851: } finally {
2852: releaseStoreProtocol(p);
2853: }
2854:
2855: if (status == null)
2856: throw new MessagingException("Cannot obtain HIGHESTMODSEQ");
2857: return status.highestmodseq;
2858: }
2859:
2860: /**
2861: * Get the messages that have been changed since the given MODSEQ value.
2862: * Also, prefetch the flags for the messages. <p>
2863: *
2864: * The server must support the CONDSTORE extension.
2865: *
2866: * @param start the first message number
2867: * @param end the last message number
2868: * @param modseq the MODSEQ value
2869: * @return the changed messages
2870: * @exception MessagingException for failures
2871: * @since JavaMail 1.5.1
2872: * @see "RFC 4551"
2873: */
2874: public synchronized Message[] getMessagesByUIDChangedSince(
2875: long start, long end, long modseq)
2876: throws MessagingException {
2877: checkOpened(); // insure that folder is open
2878:
2879: try {
2880: synchronized (messageCacheLock) {
2881: IMAPProtocol p = getProtocol();
2882: if (!p.hasCapability("CONDSTORE"))
2883: throw new BadCommandException("CONDSTORE not supported");
2884:
2885: // Issue FETCH for given range
2886: int[] nums = p.uidfetchChangedSince(start, end, modseq);
2887: return getMessagesBySeqNumbers(nums);
2888: }
2889: } catch (ConnectionException cex) {
2890: throw new FolderClosedException(this, cex.getMessage());
2891: } catch (ProtocolException pex) {
2892: throw new MessagingException(pex.getMessage(), pex);
2893: }
2894: }
2895:
2896: /**
2897: * Get the quotas for the quotaroot associated with this
2898: * folder. Note that many folders may have the same quotaroot.
2899: * Quotas are controlled on the basis of a quotaroot, not
2900: * (necessarily) a folder. The relationship between folders
2901: * and quotaroots depends on the IMAP server. Some servers
2902: * might implement a single quotaroot for all folders owned by
2903: * a user. Other servers might implement a separate quotaroot
2904: * for each folder. A single folder can even have multiple
2905: * quotaroots, perhaps controlling quotas for different
2906: * resources.
2907: *
2908: * @throws MessagingException if the server doesn't support the
2909: * QUOTA extension
2910: * @return array of Quota objects for the quotaroots associated with
2911: * this folder
2912: */
2913: public Quota[] getQuota() throws MessagingException {
2914: return (Quota[]) doOptionalCommand("QUOTA not supported",
2915: new ProtocolCommand() {
2916: @Override
2917: public Object doCommand(IMAPProtocol p)
2918: throws ProtocolException {
2919: return p.getQuotaRoot(fullName);
2920: }
2921: });
2922: }
2923:
2924: /**
2925: * Set the quotas for the quotaroot specified in the quota argument.
2926: * Typically this will be one of the quotaroots associated with this
2927: * folder, as obtained from the <code>getQuota</code> method, but it
2928: * need not be.
2929: *
2930: * @throws MessagingException if the server doesn't support the
2931: * QUOTA extension
2932: * @param quota the quota to set
2933: */
2934: public void setQuota(final Quota quota) throws MessagingException {
2935: doOptionalCommand("QUOTA not supported",
2936: new ProtocolCommand() {
2937: @Override
2938: public Object doCommand(IMAPProtocol p)
2939: throws ProtocolException {
2940: p.setQuota(quota);
2941: return null;
2942: }
2943: });
2944: }
2945:
2946: /**
2947: * Get the access control list entries for this folder.
2948: *
2949: * @throws MessagingException if the server doesn't support the
2950: * ACL extension
2951: * @return array of access control list entries
2952: */
2953: public ACL[] getACL() throws MessagingException {
2954: return (ACL[]) doOptionalCommand("ACL not supported",
2955: new ProtocolCommand() {
2956: @Override
2957: public Object doCommand(IMAPProtocol p)
2958: throws ProtocolException {
2959: return p.getACL(fullName);
2960: }
2961: });
2962: }
2963:
2964: /**
2965: * Add an access control list entry to the access control list
2966: * for this folder.
2967: *
2968: * @throws MessagingException if the server doesn't support the
2969: * ACL extension
2970: * @param acl the access control list entry to add
2971: */
2972: public void addACL(ACL acl) throws MessagingException {
2973: setACL(acl, '\0');
2974: }
2975:
2976: /**
2977: * Remove any access control list entry for the given identifier
2978: * from the access control list for this folder.
2979: *
2980: * @throws MessagingException if the server doesn't support the
2981: * ACL extension
2982: * @param name the identifier for which to remove all ACL entries
2983: */
2984: public void removeACL(final String name) throws MessagingException {
2985: doOptionalCommand("ACL not supported",
2986: new ProtocolCommand() {
2987: @Override
2988: public Object doCommand(IMAPProtocol p)
2989: throws ProtocolException {
2990: p.deleteACL(fullName, name);
2991: return null;
2992: }
2993: });
2994: }
2995:
2996: /**
2997: * Add the rights specified in the ACL to the entry for the
2998: * identifier specified in the ACL. If an entry for the identifier
2999: * doesn't already exist, add one.
3000: *
3001: * @throws MessagingException if the server doesn't support the
3002: * ACL extension
3003: * @param acl the identifer and rights to add
3004: */
3005: public void addRights(ACL acl) throws MessagingException {
3006: setACL(acl, '+');
3007: }
3008:
3009: /**
3010: * Remove the rights specified in the ACL from the entry for the
3011: * identifier specified in the ACL.
3012: *
3013: * @throws MessagingException if the server doesn't support the
3014: * ACL extension
3015: * @param acl the identifer and rights to remove
3016: */
3017: public void removeRights(ACL acl) throws MessagingException {
3018: setACL(acl, '-');
3019: }
3020:
3021: /**
3022: * Get all the rights that may be allowed to the given identifier.
3023: * Rights are grouped per RFC 2086 and each group is returned as an
3024: * element of the array. The first element of the array is the set
3025: * of rights that are always granted to the identifier. Later
3026: * elements are rights that may be optionally granted to the
3027: * identifier. <p>
3028: *
3029: * Note that this method lists the rights that it is possible to
3030: * assign to the given identifier, <em>not</em> the rights that are
3031: * actually granted to the given identifier. For the latter, see
3032: * the <code>getACL</code> method.
3033: *
3034: * @throws MessagingException if the server doesn't support the
3035: * ACL extension
3036: * @param name the identifier to list rights for
3037: * @return array of Rights objects representing possible
3038: * rights for the identifier
3039: */
3040: public Rights[] listRights(final String name) throws MessagingException {
3041: return (Rights[]) doOptionalCommand("ACL not supported",
3042: new ProtocolCommand() {
3043: @Override
3044: public Object doCommand(IMAPProtocol p)
3045: throws ProtocolException {
3046: return p.listRights(fullName, name);
3047: }
3048: });
3049: }
3050:
3051: /**
3052: * Get the rights allowed to the currently authenticated user.
3053: *
3054: * @throws MessagingException if the server doesn't support the
3055: * ACL extension
3056: * @return the rights granted to the current user
3057: */
3058: public Rights myRights() throws MessagingException {
3059: return (Rights) doOptionalCommand("ACL not supported",
3060: new ProtocolCommand() {
3061: @Override
3062: public Object doCommand(IMAPProtocol p)
3063: throws ProtocolException {
3064: return p.myRights(fullName);
3065: }
3066: });
3067: }
3068:
3069: private void setACL(final ACL acl, final char mod)
3070: throws MessagingException {
3071: doOptionalCommand("ACL not supported",
3072: new ProtocolCommand() {
3073: @Override
3074: public Object doCommand(IMAPProtocol p)
3075: throws ProtocolException {
3076: p.setACL(fullName, mod, acl);
3077: return null;
3078: }
3079: });
3080: }
3081:
3082: /**
3083: * Get the attributes that the IMAP server returns with the
3084: * LIST response.
3085: *
3086: * @return array of attributes for this folder
3087: * @exception MessagingException for failures
3088: * @since JavaMail 1.3.3
3089: */
3090: public synchronized String[] getAttributes() throws MessagingException {
3091: checkExists();
3092: if (attributes == null)
3093: exists(); // do a LIST to set the attributes
3094: return attributes == null ? new String[0] : attributes.clone();
3095: }
3096:
3097: /**
3098: * Use the IMAP IDLE command (see
3099: * <A HREF="http://www.ietf.org/rfc/rfc2177.txt">RFC 2177</A>),
3100: * if supported by the server, to enter idle mode so that the server
3101: * can send unsolicited notifications of new messages arriving, etc.
3102: * without the need for the client to constantly poll the server.
3103: * Use an appropriate listener to be notified of new messages or
3104: * other events. When another thread (e.g., the listener thread)
3105: * needs to issue an IMAP comand for this folder, the idle mode will
3106: * be terminated and this method will return. Typically the caller
3107: * will invoke this method in a loop. <p>
3108: *
3109: * The mail.imap.minidletime property enforces a minimum delay
3110: * before returning from this method, to ensure that other threads
3111: * have a chance to issue commands before the caller invokes this
3112: * method again. The default delay is 10 milliseconds.
3113: *
3114: * @throws MessagingException if the server doesn't support the
3115: * IDLE extension
3116: * @throws IllegalStateException if the folder isn't open
3117: * @since JavaMail 1.4.1
3118: */
3119: public void idle() throws MessagingException {
3120: idle(false);
3121: }
3122:
3123: /**
3124: * Like {@link #idle}, but if <code>once</code> is true, abort the
3125: * IDLE command after the first notification, to allow the caller
3126: * to process any notification synchronously.
3127: *
3128: * @throws MessagingException if the server doesn't support the
3129: * IDLE extension
3130: * @throws IllegalStateException if the folder isn't open
3131: * @param once only do one notification?
3132: * @since JavaMail 1.4.3
3133: */
3134: public void idle(boolean once) throws MessagingException {
3135: synchronized (this) {
3136: /*
3137: * We can't support the idle method if we're using SocketChannels
3138: * because SocketChannels don't allow simultaneous read and write.
3139: * If we're blocked in a read waiting for IDLE responses, we can't
3140: * send the DONE message to abort the IDLE. Sigh.
3141: * XXX - We could do select here too, like IdleManager, instead
3142: * of blocking in read, but that's more complicated.
3143: */
3144: if (protocol != null && protocol.getChannel() != null)
3145: throw new MessagingException(
3146: "idle method not supported with SocketChannels");
3147: }
3148: if (!startIdle(null))
3149: return;
3150:
3151: /*
3152: * We gave up the folder lock so that other threads
3153: * can get into the folder far enough to see that we're
3154: * in IDLE and abort the IDLE.
3155: *
3156: * Now we read responses from the IDLE command, especially
3157: * including unsolicited notifications from the server.
3158: * We don't hold the messageCacheLock while reading because
3159: * it protects the idleState and other threads need to be
3160: * able to examine the state.
3161: *
3162: * The messageCacheLock is held in handleIdle while processing
3163: * the responses so that we can update the number of messages
3164: * in the folder (for example).
3165: */
3166: for (; ; ) {
3167: if (!handleIdle(once))
3168: break;
3169: }
3170:
3171: /*
3172: * Enforce a minimum delay to give time to threads
3173: * processing the responses that came in while we
3174: * were idle.
3175: */
3176: int minidle = ((IMAPStore) store).getMinIdleTime();
3177: if (minidle > 0) {
3178: try {
3179: Thread.sleep(minidle);
3180: } catch (InterruptedException ex) {
3181: // restore the interrupted state, which callers might depend on
3182: Thread.currentThread().interrupt();
3183: }
3184: }
3185: }
3186:
3187: /**
3188: * Start the IDLE command and put this folder into the IDLE state.
3189: * IDLE processing is done later in handleIdle(), e.g., called from
3190: * the IdleManager.
3191: *
3192: * @throws MessagingException if the server doesn't support the
3193: * IDLE extension
3194: * @throws IllegalStateException if the folder isn't open
3195: * @return true if IDLE started, false otherwise
3196: * @since JavaMail 1.5.2
3197: */
3198: boolean startIdle(final IdleManager im) throws MessagingException {
3199: // ASSERT: Must NOT be called with this folder's
3200: // synchronization lock held.
3201: assert !Thread.holdsLock(this);
3202: synchronized (this) {
3203: checkOpened();
3204: if (im != null && idleManager != null && im != idleManager)
3205: throw new MessagingException(
3206: "Folder already being watched by another IdleManager");
3207: Boolean started = (Boolean) doOptionalCommand("IDLE not supported",
3208: new ProtocolCommand() {
3209: @Override
3210: public Object doCommand(IMAPProtocol p)
3211: throws ProtocolException {
3212: // if the IdleManager is already watching this folder,
3213: // there's nothing to do here
3214: if (idleState == IDLE &&
3215: im != null && im == idleManager)
3216: return Boolean.TRUE; // already watching it
3217: if (idleState == RUNNING) {
3218: p.idleStart();
3219: logger.finest("startIdle: set to IDLE");
3220: idleState = IDLE;
3221: idleManager = im;
3222: return Boolean.TRUE;
3223: } else {
3224: // some other thread must be running the IDLE
3225: // command, we'll just wait for it to finish
3226: // without aborting it ourselves
3227: try {
3228: // give up lock and wait to be not idle
3229: messageCacheLock.wait();
3230: } catch (InterruptedException ex) {
3231: // restore the interrupted state, which callers
3232: // might depend on
3233: Thread.currentThread().interrupt();
3234: }
3235: return Boolean.FALSE;
3236: }
3237: }
3238: });
3239: logger.log(Level.FINEST, "startIdle: return {0}", started);
3240: return started.booleanValue();
3241: }
3242: }
3243:
3244: /**
3245: * Read a response from the server while we're in the IDLE state.
3246: * We hold the messageCacheLock while processing the
3247: * responses so that we can update the number of messages
3248: * in the folder (for example).
3249: *
3250: * @throws MessagingException for errors
3251: * @param once only do one notification?
3252: * @return true if we should look for more IDLE responses,
3253: * false if IDLE is done
3254: * @since JavaMail 1.5.2
3255: */
3256: boolean handleIdle(boolean once) throws MessagingException {
3257: Response r = null;
3258: do {
3259: r = protocol.readIdleResponse();
3260: try {
3261: synchronized (messageCacheLock) {
3262: if (r.isBYE() && r.isSynthetic() && idleState == IDLE) {
3263: /*
3264: * If it was a timeout and no bytes were transferred
3265: * we ignore it and go back and read again.
3266: * If the I/O was otherwise interrupted, and no
3267: * bytes were transferred, we take it as a request
3268: * to abort the IDLE.
3269: */
3270: Exception ex = r.getException();
3271: if (ex instanceof InterruptedIOException &&
3272: ((InterruptedIOException) ex).
3273: bytesTransferred == 0) {
3274: if (ex instanceof SocketTimeoutException) {
3275: logger.finest(
3276: "handleIdle: ignoring socket timeout");
3277: r = null; // repeat do/while loop
3278: } else {
3279: logger.finest("handleIdle: interrupting IDLE");
3280: IdleManager im = idleManager;
3281: if (im != null) {
3282: logger.finest(
3283: "handleIdle: request IdleManager to abort");
3284: im.requestAbort(this);
3285: } else {
3286: logger.finest("handleIdle: abort IDLE");
3287: protocol.idleAbort();
3288: idleState = ABORTING;
3289: }
3290: // normally will exit the do/while loop
3291: }
3292: continue;
3293: }
3294: }
3295: boolean done = true;
3296: try {
3297: if (protocol == null ||
3298: !protocol.processIdleResponse(r))
3299: return false; // done
3300: done = false;
3301: } finally {
3302: if (done) {
3303: logger.finest("handleIdle: set to RUNNING");
3304: idleState = RUNNING;
3305: idleManager = null;
3306: messageCacheLock.notifyAll();
3307: }
3308: }
3309: if (once) {
3310: if (idleState == IDLE) {
3311: try {
3312: protocol.idleAbort();
3313: } catch (Exception ex) {
3314: // ignore any failures, still have to abort.
3315: // connection failures will be detected above
3316: // in the call to readIdleResponse.
3317: }
3318: idleState = ABORTING;
3319: }
3320: }
3321: }
3322: } catch (ConnectionException cex) {
3323: // Oops, the folder died on us.
3324: throw new FolderClosedException(this, cex.getMessage());
3325: } catch (ProtocolException pex) {
3326: throw new MessagingException(pex.getMessage(), pex);
3327: }
3328: // keep processing responses already in our buffer
3329: } while (r == null || protocol.hasResponse());
3330: return true;
3331: }
3332:
3333: /*
3334: * If an IDLE command is in progress, abort it if necessary,
3335: * and wait until it completes.
3336: * ASSERT: Must be called with the message cache lock held.
3337: */
3338: void waitIfIdle() throws ProtocolException {
3339: assert Thread.holdsLock(messageCacheLock);
3340: while (idleState != RUNNING) {
3341: if (idleState == IDLE) {
3342: IdleManager im = idleManager;
3343: if (im != null) {
3344: logger.finest("waitIfIdle: request IdleManager to abort");
3345: im.requestAbort(this);
3346: } else {
3347: logger.finest("waitIfIdle: abort IDLE");
3348: protocol.idleAbort();
3349: idleState = ABORTING;
3350: }
3351: } else
3352: logger.log(Level.FINEST, "waitIfIdle: idleState {0}", idleState);
3353: try {
3354: // give up lock and wait to be not idle
3355: if (logger.isLoggable(Level.FINEST))
3356: logger.finest("waitIfIdle: wait to be not idle: " +
3357: Thread.currentThread());
3358: messageCacheLock.wait();
3359: if (logger.isLoggable(Level.FINEST))
3360: logger.finest("waitIfIdle: wait done, idleState " +
3361: idleState + ": " + Thread.currentThread());
3362: } catch (InterruptedException ex) {
3363: // restore the interrupted state, which callers might depend on
3364: Thread.currentThread().interrupt();
3365: // If someone is trying to interrupt us we can't keep going
3366: // around the loop waiting for IDLE to complete, but we can't
3367: // just return because callers expect the idleState to be
3368: // RUNNING when we return. Throwing this exception seems
3369: // like the best choice.
3370: throw new ProtocolException("Interrupted waitIfIdle", ex);
3371: }
3372: }
3373: }
3374:
3375: /*
3376: * Send the DONE command that aborts the IDLE; used by IdleManager.
3377: */
3378: void idleAbort() {
3379: synchronized (messageCacheLock) {
3380: if (idleState == IDLE && protocol != null) {
3381: protocol.idleAbort();
3382: idleState = ABORTING;
3383: }
3384: }
3385: }
3386:
3387: /*
3388: * Send the DONE command that aborts the IDLE and wait for the response;
3389: * used by IdleManager.
3390: */
3391: void idleAbortWait() {
3392: synchronized (messageCacheLock) {
3393: if (idleState == IDLE && protocol != null) {
3394: protocol.idleAbort();
3395: idleState = ABORTING;
3396:
3397: // read responses until OK or connection failure
3398: try {
3399: for (; ; ) {
3400: if (!handleIdle(false))
3401: break;
3402: }
3403: } catch (Exception ex) {
3404: // assume it's a connection failure; nothing more to do
3405: logger.log(Level.FINEST, "Exception in idleAbortWait", ex);
3406: }
3407: logger.finest("IDLE aborted");
3408: }
3409: }
3410: }
3411:
3412: /**
3413: * Return the SocketChannel for this connection, if any, for use
3414: * in IdleManager.
3415: */
3416: SocketChannel getChannel() {
3417: return protocol != null ? protocol.getChannel() : null;
3418: }
3419:
3420: /**
3421: * Send the IMAP ID command (if supported by the server) and return
3422: * the result from the server. The ID command identfies the client
3423: * to the server and returns information about the server to the client.
3424: * See <A HREF="http://www.ietf.org/rfc/rfc2971.txt">RFC 2971</A>.
3425: * The returned Map is unmodifiable.
3426: *
3427: * @throws MessagingException if the server doesn't support the
3428: * ID extension
3429: * @param clientParams a Map of keys and values identifying the client
3430: * @return a Map of keys and values identifying the server
3431: * @since JavaMail 1.5.1
3432: */
3433: @SuppressWarnings("unchecked")
3434: public Map<String, String> id(final Map<String, String> clientParams)
3435: throws MessagingException {
3436: checkOpened();
3437: return (Map<String, String>) doOptionalCommand("ID not supported",
3438: new ProtocolCommand() {
3439: @Override
3440: public Object doCommand(IMAPProtocol p)
3441: throws ProtocolException {
3442: return p.id(clientParams);
3443: }
3444: });
3445: }
3446:
3447: /**
3448: * Use the IMAP STATUS command to get the indicated item.
3449: * The STATUS item may be a standard item such as "RECENT" or "UNSEEN",
3450: * or may be a server-specific item.
3451: * The folder must be closed. If the item is not found, or the
3452: * folder is open, -1 is returned.
3453: *
3454: * @throws MessagingException for errors
3455: * @param item the STATUS item to fetch
3456: * @return the value of the STATUS item, or -1
3457: * @since JavaMail 1.5.2
3458: */
3459: public synchronized long getStatusItem(String item)
3460: throws MessagingException {
3461: if (!opened) {
3462: checkExists();
3463:
3464: IMAPProtocol p = null;
3465: Status status = null;
3466: try {
3467: p = getStoreProtocol(); // XXX
3468: String[] items = {item};
3469: status = p.status(fullName, items);
3470: return status != null ? status.getItem(item) : -1;
3471: } catch (BadCommandException bex) {
3472: // doesn't support STATUS, probably vanilla IMAP4 ..
3473: // Could EXAMINE, SEARCH for UNREAD messages and
3474: // return the count .. bah, not worth it.
3475: return -1;
3476: } catch (ConnectionException cex) {
3477: throw new StoreClosedException(store, cex.getMessage());
3478: } catch (ProtocolException pex) {
3479: throw new MessagingException(pex.getMessage(), pex);
3480: } finally {
3481: releaseStoreProtocol(p);
3482: }
3483: }
3484: return -1;
3485: }
3486:
3487: /**
3488: * The response handler. This is the callback routine that is
3489: * invoked by the protocol layer.
3490: */
3491: /*
3492: * ASSERT: This method must be called only when holding the
3493: * messageCacheLock.
3494: * ASSERT: This method must *not* invoke any other method that
3495: * might grab the 'folder' lock or 'message' lock (i.e., any
3496: * synchronized methods on IMAPFolder or IMAPMessage)
3497: * since that will result in violating the locking hierarchy.
3498: */
3499: @Override
3500: public void handleResponse(Response r) {
3501: assert Thread.holdsLock(messageCacheLock);
3502:
3503: /*
3504: * First, delegate possible ALERT or notification to the Store.
3505: */
3506: if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
3507: ((IMAPStore) store).handleResponseCode(r);
3508:
3509: /*
3510: * Now check whether this is a BYE or OK response and
3511: * handle appropriately.
3512: */
3513: if (r.isBYE()) {
3514: if (opened) // XXX - accessed without holding folder lock
3515: cleanup(false);
3516: return;
3517: } else if (r.isOK()) {
3518: // HIGHESTMODSEQ can be updated on any OK response
3519: r.skipSpaces();
3520: if (r.readByte() == '[') {
3521: String s = r.readAtom();
3522: if (s.equalsIgnoreCase("HIGHESTMODSEQ"))
3523: highestmodseq = r.readLong();
3524: }
3525: r.reset();
3526: return;
3527: } else if (!r.isUnTagged()) {
3528: return; // might be a continuation for IDLE
3529: }
3530:
3531: /* Now check whether this is an IMAP specific response */
3532: if (!(r instanceof IMAPResponse)) {
3533: // Probably a bug in our code !
3534: // XXX - should be an assert
3535: logger.fine("UNEXPECTED RESPONSE : " + r.toString());
3536: return;
3537: }
3538:
3539: IMAPResponse ir = (IMAPResponse) r;
3540:
3541: if (ir.keyEquals("EXISTS")) { // EXISTS
3542: int exists = ir.getNumber();
3543: if (exists <= realTotal)
3544: // Could be the EXISTS following EXPUNGE, ignore 'em
3545: return;
3546:
3547: int count = exists - realTotal; // number of new messages
3548: Message[] msgs = new Message[count];
3549:
3550: // Add 'count' new IMAPMessage objects into the messageCache
3551: messageCache.addMessages(count, realTotal + 1);
3552: int oldtotal = total; // used in loop below
3553: realTotal += count;
3554: total += count;
3555:
3556: // avoid instantiating Message objects if no listeners.
3557: if (hasMessageCountListener) {
3558: for (int i = 0; i < count; i++)
3559: msgs[i] = messageCache.getMessage(++oldtotal);
3560:
3561: // Notify listeners.
3562: notifyMessageAddedListeners(msgs);
3563: }
3564:
3565: } else if (ir.keyEquals("EXPUNGE")) {
3566: // EXPUNGE response.
3567:
3568: int seqnum = ir.getNumber();
3569: if (seqnum > realTotal) {
3570: // A message was expunged that we never knew about.
3571: // Exchange will do this. Just ignore the notification.
3572: // (Alternatively, we could simulate an EXISTS for the
3573: // expunged message before expunging it.)
3574: return;
3575: }
3576: Message[] msgs = null;
3577: if (doExpungeNotification && hasMessageCountListener) {
3578: // save the Message object first; can't look it
3579: // up after it's expunged
3580: msgs = new Message[]{getMessageBySeqNumber(seqnum)};
3581: if (msgs[0] == null) // XXX - should never happen
3582: msgs = null;
3583: }
3584:
3585: messageCache.expungeMessage(seqnum);
3586:
3587: // decrement 'realTotal'; but leave 'total' unchanged
3588: realTotal--;
3589:
3590: if (msgs != null) // Do the notification here.
3591: notifyMessageRemovedListeners(false, msgs);
3592:
3593: } else if (ir.keyEquals("VANISHED")) {
3594: // after the folder is opened with QRESYNC, a VANISHED response
3595: // without the (EARLIER) tag is used instead of the EXPUNGE
3596: // response
3597:
3598: // "VANISHED" SP ["(EARLIER)"] SP known-uids
3599: String[] s = ir.readAtomStringList();
3600: if (s == null) { // no (EARLIER)
3601: String uids = ir.readAtom();
3602: UIDSet[] uidset = UIDSet.parseUIDSets(uids);
3603: // assume no duplicates and no UIDs out of range
3604: realTotal -= UIDSet.size(uidset);
3605: long[] luid = UIDSet.toArray(uidset);
3606: Message[] msgs = createMessagesForUIDs(luid);
3607: for (Message m : msgs) {
3608: if (m.getMessageNumber() > 0)
3609: messageCache.expungeMessage(m.getMessageNumber());
3610: }
3611: if (doExpungeNotification && hasMessageCountListener) {
3612: notifyMessageRemovedListeners(true, msgs);
3613: }
3614: } // else if (EARLIER), ignore
3615:
3616: } else if (ir.keyEquals("FETCH")) {
3617: assert ir instanceof FetchResponse : "!ir instanceof FetchResponse";
3618: Message msg = processFetchResponse((FetchResponse) ir);
3619: if (msg != null)
3620: notifyMessageChangedListeners(
3621: MessageChangedEvent.FLAGS_CHANGED, msg);
3622:
3623: } else if (ir.keyEquals("RECENT")) {
3624: // update 'recent'
3625: recent = ir.getNumber();
3626: }
3627: }
3628:
3629: /**
3630: * Process a FETCH response.
3631: * The only unsolicited FETCH response that makes sense
3632: * to me (for now) is FLAGS updates, which might include
3633: * UID and MODSEQ information. Ignore any other junk.
3634: */
3635: private Message processFetchResponse(FetchResponse fr) {
3636: IMAPMessage msg = getMessageBySeqNumber(fr.getNumber());
3637: if (msg != null) { // should always be true
3638: boolean notify = false;
3639:
3640: UID uid = fr.getItem(UID.class);
3641: if (uid != null && msg.getUID() != uid.uid) {
3642: msg.setUID(uid.uid);
3643: if (uidTable == null)
3644: uidTable = new Hashtable<>();
3645: uidTable.put(Long.valueOf(uid.uid), msg);
3646: notify = true;
3647: }
3648:
3649: MODSEQ modseq = fr.getItem(MODSEQ.class);
3650: if (modseq != null && msg._getModSeq() != modseq.modseq) {
3651: msg.setModSeq(modseq.modseq);
3652:                 /*
3653:                  * XXX - should we update the folder's HIGHESTMODSEQ or not?
3654:                  *
3655:                 if (modseq.modseq > highestmodseq)
3656:                  highestmodseq = modseq.modseq;
3657:                  */
3658: notify = true;
3659: }
3660:
3661: // Get FLAGS response, if present
3662: FLAGS flags = fr.getItem(FLAGS.class);
3663: if (flags != null) {
3664: msg._setFlags(flags); // assume flags changed
3665: notify = true;
3666: }
3667:
3668: // handle any extension items that might've changed
3669: // XXX - no notifications associated with extension items
3670: msg.handleExtensionFetchItems(fr.getExtensionItems());
3671:
3672: if (!notify)
3673: msg = null;
3674: }
3675: return msg;
3676: }
3677:
3678: /**
3679: * Handle the given array of Responses.
3680: *
3681: * ASSERT: This method must be called only when holding the
3682: * messageCacheLock
3683: */
3684: void handleResponses(Response[] r) {
3685: for (int i = 0; i < r.length; i++) {
3686: if (r[i] != null)
3687: handleResponse(r[i]);
3688: }
3689: }
3690:
3691: /**
3692: * Get this folder's Store's protocol connection.
3693: *
3694: * When acquiring a store protocol object, it is important to
3695: * use the following steps:
3696: *
3697: * <blockquote><pre>
3698: * IMAPProtocol p = null;
3699: * try {
3700: * p = getStoreProtocol();
3701: * // perform the command
3702: * } catch (WhateverException ex) {
3703: * // handle it
3704: * } finally {
3705: * releaseStoreProtocol(p);
3706: * }
3707: * </pre></blockquote>
3708: *
3709: * ASSERT: Must be called with this folder's synchronization lock held.
3710: *
3711: * @return the IMAPProtocol for the Store's connection
3712: * @exception ProtocolException for protocol errors
3713: */
3714: protected synchronized IMAPProtocol getStoreProtocol()
3715: throws ProtocolException {
3716: connectionPoolLogger.fine("getStoreProtocol() borrowing a connection");
3717: return ((IMAPStore) store).getFolderStoreProtocol();
3718: }
3719:
3720: /**
3721: * Throw the appropriate 'closed' exception.
3722: *
3723: * @param cex the ConnectionException
3724: * @exception FolderClosedException if the folder is closed
3725: * @exception StoreClosedException if the store is closed
3726: */
3727: protected synchronized void throwClosedException(ConnectionException cex)
3728: throws FolderClosedException, StoreClosedException {
3729: // If it's the folder's protocol object, throw a FolderClosedException;
3730: // otherwise, throw a StoreClosedException.
3731: // If a command has failed because the connection is closed,
3732: // the folder will have already been forced closed by the
3733: // time we get here and our protocol object will have been
3734: // released, so if we no longer have a protocol object we base
3735: // this decision on whether we *think* the folder is open.
3736: if ((protocol != null && cex.getProtocol() == protocol) ||
3737: (protocol == null && !reallyClosed))
3738: throw new FolderClosedException(this, cex.getMessage());
3739: else
3740: throw new StoreClosedException(store, cex.getMessage());
3741: }
3742:
3743: /**
3744: * Return the IMAPProtocol object for this folder. <p>
3745: *
3746: * This method will block if necessary to wait for an IDLE
3747: * command to finish.
3748: *
3749: * @return the IMAPProtocol object used when the folder is open
3750: * @exception ProtocolException for protocol errors
3751: */
3752: protected IMAPProtocol getProtocol() throws ProtocolException {
3753: assert Thread.holdsLock(messageCacheLock);
3754: waitIfIdle();
3755: // if we no longer have a protocol object after waiting, it probably
3756: // means the connection has been closed due to a communnication error,
3757: // or possibly because the folder has been closed
3758: if (protocol == null)
3759: throw new ConnectionException("Connection closed");
3760: return protocol;
3761: }
3762:
3763: /**
3764: * A simple interface for user-defined IMAP protocol commands.
3765: */
3766: public static interface ProtocolCommand {
3767: /**
3768: * Execute the user-defined command using the supplied IMAPProtocol
3769: * object.
3770: *
3771: * @param protocol the IMAPProtocol for the connection
3772: * @return the results of the command
3773: * @exception ProtocolException for protocol errors
3774: */
3775: public Object doCommand(IMAPProtocol protocol) throws ProtocolException;
3776: }
3777:
3778: /**
3779: * Execute a user-supplied IMAP command. The command is executed
3780: * in the appropriate context with the necessary locks held and
3781: * using the appropriate <code>IMAPProtocol</code> object. <p>
3782: *
3783: * This method returns whatever the <code>ProtocolCommand</code>
3784: * object's <code>doCommand</code> method returns. If the
3785: * <code>doCommand</code> method throws a <code>ConnectionException</code>
3786: * it is translated into a <code>StoreClosedException</code> or
3787: * <code>FolderClosedException</code> as appropriate. If the
3788: * <code>doCommand</code> method throws a <code>ProtocolException</code>
3789: * it is translated into a <code>MessagingException</code>. <p>
3790: *
3791: * The following example shows how to execute the IMAP NOOP command.
3792: * Executing more complex IMAP commands requires intimate knowledge
3793: * of the <code>org.eclipse.angus.mail.iap</code> and
3794: * <code>org.eclipse.angus.mail.imap.protocol</code> packages, best acquired by
3795: * reading the source code.
3796: *
3797: * <blockquote><pre>
3798: * import org.eclipse.angus.mail.iap.*;
3799: * import org.eclipse.angus.mail.imap.*;
3800: * import org.eclipse.angus.mail.imap.protocol.*;
3801: *
3802: * ...
3803: *
3804: * IMAPFolder f = (IMAPFolder)folder;
3805: * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
3806: *         public Object doCommand(IMAPProtocol p)
3807: *                         throws ProtocolException {
3808: *          p.simpleCommand("NOOP", null);
3809: *          return null;
3810: * }
3811: * });
3812: * </pre></blockquote>
3813: * <p>
3814: *
3815: * Here's a more complex example showing how to use the proposed
3816: * IMAP SORT extension:
3817: *
3818: * <blockquote><pre>
3819: * import org.eclipse.angus.mail.iap.*;
3820: * import org.eclipse.angus.mail.imap.*;
3821: * import org.eclipse.angus.mail.imap.protocol.*;
3822: *
3823: * ...
3824: *
3825: * IMAPFolder f = (IMAPFolder)folder;
3826: * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
3827: *         public Object doCommand(IMAPProtocol p)
3828: *                         throws ProtocolException {
3829: *          // Issue command
3830: *          Argument args = new Argument();
3831: *          Argument list = new Argument();
3832: *          list.writeString("SUBJECT");
3833: *          args.writeArgument(list);
3834: *          args.writeString("UTF-8");
3835: *          args.writeString("ALL");
3836: *          Response[] r = p.command("SORT", args);
3837: *          Response response = r[r.length-1];
3838: *
3839: *          // Grab response
3840: *          Vector v = new Vector();
3841: *          if (response.isOK()) { // command succesful
3842: *                 for (int i = 0, len = r.length; i < len; i++) {
3843: *                  if (!(r[i] instanceof IMAPResponse))
3844: *                         continue;
3845: *
3846: *                  IMAPResponse ir = (IMAPResponse)r[i];
3847: *                  if (ir.keyEquals("SORT")) {
3848: *                         String num;
3849: *                         while ((num = ir.readAtomString()) != null)
3850: *                          System.out.println(num);
3851: *                         r[i] = null;
3852: * }
3853: * }
3854: * }
3855: *
3856: *          // dispatch remaining untagged responses
3857: *          p.notifyResponseHandlers(r);
3858: *          p.handleResult(response);
3859: *
3860: *          return null;
3861: * }
3862: * });
3863: * </pre></blockquote>
3864: *
3865: * @param cmd the protocol command
3866: * @return the result of the command
3867: * @exception MessagingException for failures
3868: */
3869: public Object doCommand(ProtocolCommand cmd) throws MessagingException {
3870: try {
3871: return doProtocolCommand(cmd);
3872: } catch (ConnectionException cex) {
3873: // Oops, the store or folder died on us.
3874: throwClosedException(cex);
3875: } catch (ProtocolException pex) {
3876: throw new MessagingException(pex.getMessage(), pex);
3877: }
3878: return null;
3879: }
3880:
3881: public Object doOptionalCommand(String err, ProtocolCommand cmd)
3882: throws MessagingException {
3883: try {
3884: return doProtocolCommand(cmd);
3885: } catch (BadCommandException bex) {
3886: throw new MessagingException(err, bex);
3887: } catch (ConnectionException cex) {
3888: // Oops, the store or folder died on us.
3889: throwClosedException(cex);
3890: } catch (ProtocolException pex) {
3891: throw new MessagingException(pex.getMessage(), pex);
3892: }
3893: return null;
3894: }
3895:
3896: public Object doCommandIgnoreFailure(ProtocolCommand cmd)
3897: throws MessagingException {
3898: try {
3899: return doProtocolCommand(cmd);
3900: } catch (CommandFailedException cfx) {
3901: return null;
3902: } catch (ConnectionException cex) {
3903: // Oops, the store or folder died on us.
3904: throwClosedException(cex);
3905: } catch (ProtocolException pex) {
3906: throw new MessagingException(pex.getMessage(), pex);
3907: }
3908: return null;
3909: }
3910:
3911: protected synchronized Object doProtocolCommand(ProtocolCommand cmd)
3912: throws ProtocolException {
3913: /*
3914: * Check whether we have a protocol object, not whether we're
3915: * opened, to allow use of the exsting protocol object in the
3916: * open method before the state is changed to "opened".
3917: */
3918: if (protocol != null) {
3919: synchronized (messageCacheLock) {
3920: return cmd.doCommand(getProtocol());
3921: }
3922: }
3923:
3924: // only get here if using store's connection
3925: IMAPProtocol p = null;
3926:
3927: try {
3928: p = getStoreProtocol();
3929: return cmd.doCommand(p);
3930: } finally {
3931: releaseStoreProtocol(p);
3932: }
3933: }
3934:
3935: /**
3936: * Release the store protocol object. If we borrowed a protocol
3937: * object from the connection pool, give it back. If we used our
3938: * own protocol object, nothing to do.
3939: *
3940: * ASSERT: Must be called with this folder's synchronization lock held.
3941: *
3942: * @param p the IMAPProtocol object
3943: */
3944: protected synchronized void releaseStoreProtocol(IMAPProtocol p) {
3945: if (p != protocol)
3946: ((IMAPStore) store).releaseFolderStoreProtocol(p);
3947: else {
3948: // XXX - should never happen
3949: logger.fine("releasing our protocol as store protocol?");
3950: }
3951: }
3952:
3953: /**
3954: * Release the protocol object.
3955: *
3956: * ASSERT: This method must be called only when holding the
3957: * messageCacheLock
3958: *
3959: * @param returnToPool return the protocol object to the pool?
3960: */
3961: protected void releaseProtocol(boolean returnToPool) {
3962: if (protocol != null) {
3963: protocol.removeResponseHandler(this);
3964:
3965: if (returnToPool)
3966: ((IMAPStore) store).releaseProtocol(this, protocol);
3967: else {
3968: protocol.disconnect(); // make sure it's disconnected
3969: ((IMAPStore) store).releaseProtocol(this, null);
3970: }
3971: protocol = null;
3972: }
3973: }
3974:
3975: /**
3976: * Issue a noop command for the connection if the connection has not been
3977: * used in more than a second. If <code>keepStoreAlive</code> is true,
3978: * also issue a noop over the store's connection.
3979: *
3980: * ASSERT: This method must be called only when holding the
3981: * messageCacheLock
3982: *
3983: * @param keepStoreAlive keep the Store alive too?
3984: * @exception ProtocolException for protocol errors
3985: */
3986: protected void keepConnectionAlive(boolean keepStoreAlive)
3987: throws ProtocolException {
3988:
3989: assert Thread.holdsLock(messageCacheLock);
3990: if (protocol == null) // in case connection was closed
3991: return;
3992: if (System.currentTimeMillis() - protocol.getTimestamp() > 1000) {
3993: waitIfIdle();
3994: if (protocol != null)
3995: protocol.noop();
3996: }
3997:
3998: if (keepStoreAlive && ((IMAPStore) store).hasSeparateStoreConnection()) {
3999: IMAPProtocol p = null;
4000: try {
4001: p = ((IMAPStore) store).getFolderStoreProtocol();
4002: if (System.currentTimeMillis() - p.getTimestamp() > 1000)
4003: p.noop();
4004: } finally {
4005: ((IMAPStore) store).releaseFolderStoreProtocol(p);
4006: }
4007: }
4008: }
4009:
4010: /**
4011: * Get the message object for the given sequence number. If
4012: * none found, null is returned.
4013: *
4014: * ASSERT: This method must be called only when holding the
4015: * messageCacheLock
4016: *
4017: * @param seqnum the message sequence number
4018: * @return the IMAPMessage object
4019: */
4020: protected IMAPMessage getMessageBySeqNumber(int seqnum) {
4021: if (seqnum > messageCache.size()) {
4022: // Microsoft Exchange will sometimes return message
4023: // numbers that it has not yet notified the client
4024: // about via EXISTS; ignore those messages here.
4025: // GoDaddy IMAP does this too.
4026: if (logger.isLoggable(Level.FINE))
4027: logger.fine("ignoring message number " +
4028: seqnum + " outside range " + messageCache.size());
4029: return null;
4030: }
4031: return messageCache.getMessageBySeqnum(seqnum);
4032: }
4033:
4034: /**
4035: * Get the message objects for the given sequence numbers.
4036: *
4037: * ASSERT: This method must be called only when holding the
4038: * messageCacheLock
4039: *
4040: * @param seqnums the array of message sequence numbers
4041: * @return the IMAPMessage objects
4042: * @since JavaMail 1.5.3
4043: */
4044: protected IMAPMessage[] getMessagesBySeqNumbers(int[] seqnums) {
4045: IMAPMessage[] msgs = new IMAPMessage[seqnums.length];
4046: int nulls = 0;
4047: // Map seq-numbers into actual Messages.
4048: for (int i = 0; i < seqnums.length; i++) {
4049: msgs[i] = getMessageBySeqNumber(seqnums[i]);
4050: if (msgs[i] == null)
4051: nulls++;
4052: }
4053: if (nulls > 0) { // compress the array to remove the nulls
4054: IMAPMessage[] nmsgs = new IMAPMessage[seqnums.length - nulls];
4055: for (int i = 0, j = 0; i < msgs.length; i++) {
4056: if (msgs[i] != null)
4057: nmsgs[j++] = msgs[i];
4058: }
4059: msgs = nmsgs;
4060: }
4061: return msgs;
4062: }
4063:
4064: private boolean isDirectory() {
4065: return ((type & HOLDS_FOLDERS) != 0);
4066: }
4067: }
4068:
4069: /**
4070: * An object that holds a Message object
4071: * and reports its size and writes it to another OutputStream
4072: * on demand. Used by appendMessages to avoid the need to
4073: * buffer the entire message in memory in a single byte array
4074: * before sending it to the server.
4075: */
4076: class MessageLiteral implements Literal {
4077: private Message msg;
4078: private int msgSize = -1;
4079: private byte[] buf; // the buffered message, if not null
4080:
4081: public MessageLiteral(Message msg, int maxsize)
4082: throws MessagingException, IOException {
4083: this.msg = msg;
4084: // compute the size here so exceptions can be returned immediately
4085: LengthCounter lc = new LengthCounter(maxsize);
4086: OutputStream os = new CRLFOutputStream(lc);
4087: msg.writeTo(os);
4088: os.flush();
4089: msgSize = lc.getSize();
4090: buf = lc.getBytes();
4091: }
4092:
4093: @Override
4094: public int size() {
4095: return msgSize;
4096: }
4097:
4098: @Override
4099: public void writeTo(OutputStream os) throws IOException {
4100: // the message should not change between the constructor and this call
4101: try {
4102: if (buf != null)
4103: os.write(buf, 0, msgSize);
4104: else {
4105: os = new CRLFOutputStream(os);
4106: msg.writeTo(os);
4107: }
4108: } catch (MessagingException mex) {
4109: // exceptions here are bad, "should" never happen
4110: throw new IOException("MessagingException while appending message: "
4111: + mex);
4112: }
4113: }
4114: }
4115:
4116: /**
4117: * Count the number of bytes written to the stream.
4118: * Also, save a copy of small messages to avoid having to process
4119: * the data again.
4120: */
4121: class LengthCounter extends OutputStream {
4122: private int size = 0;
4123: private byte[] buf;
4124: private int maxsize;
4125:
4126: public LengthCounter(int maxsize) {
4127: buf = new byte[8192];
4128: this.maxsize = maxsize;
4129: }
4130:
4131: @Override
4132: public void write(int b) {
4133: int newsize = size + 1;
4134: if (buf != null) {
4135: if (newsize > maxsize && maxsize >= 0) {
4136: buf = null;
4137: } else if (newsize > buf.length) {
4138: byte[] newbuf = new byte[Math.max(buf.length << 1, newsize)];
4139: System.arraycopy(buf, 0, newbuf, 0, size);
4140: buf = newbuf;
4141: buf[size] = (byte) b;
4142: } else {
4143: buf[size] = (byte) b;
4144: }
4145: }
4146: size = newsize;
4147: }
4148:
4149: @Override
4150: public void write(byte[] b, int off, int len) {
4151: if ((off < 0) || (off > b.length) || (len < 0) ||
4152: ((off + len) > b.length) || ((off + len) < 0)) {
4153: throw new IndexOutOfBoundsException();
4154: } else if (len == 0) {
4155: return;
4156: }
4157: int newsize = size + len;
4158: if (buf != null) {
4159: if (newsize > maxsize && maxsize >= 0) {
4160: buf = null;
4161: } else if (newsize > buf.length) {
4162: byte[] newbuf = new byte[Math.max(buf.length << 1, newsize)];
4163: System.arraycopy(buf, 0, newbuf, 0, size);
4164: buf = newbuf;
4165: System.arraycopy(b, off, buf, size, len);
4166: } else {
4167: System.arraycopy(b, off, buf, size, len);
4168: }
4169: }
4170: size = newsize;
4171: }
4172:
4173: @Override
4174: public void write(byte[] b) throws IOException {
4175: write(b, 0, b.length);
4176: }
4177:
4178: public int getSize() {
4179: return size;
4180: }
4181:
4182: public byte[] getBytes() {
4183: return buf;
4184: }
4185: }