Categories:
Audio (13)
Biotech (29)
Bytecode (36)
Database (77)
Framework (7)
Game (7)
General (507)
Graphics (53)
I/O (35)
IDE (2)
JAR Tools (101)
JavaBeans (21)
JDBC (121)
JDK (426)
JSP (20)
Logging (108)
Mail (58)
Messaging (8)
Network (84)
PDF (97)
Report (7)
Scripting (84)
Security (32)
Server (121)
Servlet (26)
SOAP (24)
Testing (54)
Web (15)
XML (309)
Collections:
Other Resources:
JavaMail 1.6.2 Source Code Files
JavaMail Source Code Files are provided in the source package file, httpcomponents-client-5.2-src.zip.
You can browse JavaMail Source Code files below:
✍: FYIcenter.com
⏎ com/sun/mail/imap/IMAPFolder.java
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2018 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://oss.oracle.com/licenses/CDDL+GPL-1.1 * or LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.mail.imap; import java.util.Date; import java.util.Vector; import java.util.Hashtable; import java.util.List; import java.util.ArrayList; import java.util.Map; import java.util.NoSuchElementException; import java.util.Locale; import java.util.logging.Level; import java.io.*; import java.net.SocketTimeoutException; import java.nio.channels.SocketChannel; import javax.mail.*; import javax.mail.event.*; import javax.mail.internet.*; import javax.mail.search.*; import com.sun.mail.util.PropUtil; import com.sun.mail.util.MailLogger; import com.sun.mail.util.CRLFOutputStream; import com.sun.mail.iap.*; import com.sun.mail.imap.protocol.*; /** * This class implements an IMAP folder. <p> * * A closed IMAPFolder object shares a protocol connection with its IMAPStore * object. When the folder is opened, it gets its own protocol connection. <p> * * Applications that need to make use of IMAP-specific features may cast * a <code>Folder</code> object to an <code>IMAPFolder</code> object and * use the methods on this class. <p> * * The {@link #getQuota getQuota} and * {@link #setQuota setQuota} methods support the IMAP QUOTA extension. * Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A> * for more information. <p> * * The {@link #getACL getACL}, {@link #addACL addACL}, * {@link #removeACL removeACL}, {@link #addRights addRights}, * {@link #removeRights removeRights}, {@link #listRights listRights}, and * {@link #myRights myRights} methods support the IMAP ACL extension. * Refer to <A HREF="http://www.ietf.org/rfc/rfc2086.txt">RFC 2086</A> * for more information. <p> * * The {@link #getSortedMessages getSortedMessages} * methods support the IMAP SORT extension. * Refer to <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A> * for more information. <p> * * The {@link #open(int,com.sun.mail.imap.ResyncData) open(int,ResyncData)} * method and {@link com.sun.mail.imap.ResyncData ResyncData} class supports * the IMAP CONDSTORE and QRESYNC extensions. * Refer to <A HREF="http://www.ietf.org/rfc/rfc4551.txt">RFC 4551</A> * and <A HREF="http://www.ietf.org/rfc/rfc5162.txt">RFC 5162</A> * for more information. <p> * * The {@link #doCommand doCommand} method and * {@link IMAPFolder.ProtocolCommand IMAPFolder.ProtocolCommand} * interface support use of arbitrary IMAP protocol commands. <p> * * See the <a href="package-summary.html">com.sun.mail.imap</a> package * documentation for further information on the IMAP protocol provider. <p> * * <strong>WARNING:</strong> The APIs unique to this class should be * considered <strong>EXPERIMENTAL</strong>. They may be changed in the * future in ways that are incompatible with applications using the * current APIs. * * @author John Mani * @author Bill Shannon * @author Jim Glennon */ /* * The folder object itself serves as a lock for the folder's state * EXCEPT for the message cache (see below), typically by using * synchronized methods. When checking that a folder is open or * closed, the folder's lock must be held. It's important that the * folder's lock is acquired before the messageCacheLock (see below). * Thus, the locking hierarchy is that the folder lock, while optional, * must be acquired before the messageCacheLock, if it's acquired at * all. Be especially careful of callbacks that occur while holding * the messageCacheLock into (e.g.) superclass Folder methods that are * synchronized. Note that methods in IMAPMessage will acquire the * messageCacheLock without acquiring the folder lock. <p> * * When a folder is opened, it creates a messageCache (a Vector) of * empty IMAPMessage objects. Each Message has a messageNumber - which * is its index into the messageCache, and a sequenceNumber - which is * its IMAP sequence-number. All operations on a Message which involve * communication with the server, use the message's sequenceNumber. <p> * * The most important thing to note here is that the server can send * unsolicited EXPUNGE notifications as part of the responses for "most" * commands. Refer RFC 3501, sections 5.3 & 5.5 for gory details. Also, * the server sends these notifications AFTER the message has been * expunged. And once a message is expunged, the sequence-numbers of * those messages after the expunged one are renumbered. This essentially * means that the mapping between *any* Message and its sequence-number * can change in the period when a IMAP command is issued and its responses * are processed. Hence we impose a strict locking model as follows: <p> * * We define one mutex per folder - this is just a Java Object (named * messageCacheLock). Any time a command is to be issued to the IMAP * server (i.e., anytime the corresponding IMAPProtocol method is * invoked), follow the below style: * * synchronized (messageCacheLock) { // ACQUIRE LOCK * issue command () * * // The response processing is typically done within * // the handleResponse() callback. A few commands (Fetch, * // Expunge) return *all* responses and hence their * // processing is done here itself. Now, as part of the * // processing unsolicited EXPUNGE responses, we renumber * // the necessary sequence-numbers. Thus the renumbering * // happens within this critical-region, surrounded by * // locks. * process responses () * } // RELEASE LOCK * * This technique is used both by methods in IMAPFolder and by methods * in IMAPMessage and other classes that operate on data in the folder. * Note that holding the messageCacheLock has the side effect of * preventing the folder from being closed, and thus ensuring that the * folder's protocol object is still valid. The protocol object should * only be accessed while holding the messageCacheLock (except for calls * to IMAPProtocol.isREV1(), which don't need to be protected because it * doesn't access the server). * * Note that interactions with the Store's protocol connection do * not have to be protected as above, since the Store's protocol is * never in a "meaningful" SELECT-ed state. */ public class IMAPFolder extends Folder implements UIDFolder, ResponseHandler { protected volatile String fullName; // full name protected String name; // name protected int type; // folder type. protected char separator; // separator protected Flags availableFlags; // available flags protected Flags permanentFlags; // permanent flags protected volatile boolean exists; // whether this folder really exists ? protected boolean isNamespace = false; // folder is a namespace name protected volatile String[] attributes;// name attributes from LIST response protected volatile IMAPProtocol protocol; // this folder's protocol object protected MessageCache messageCache;// message cache // accessor lock for message cache protected final Object messageCacheLock = new Object(); protected Hashtable<Long, IMAPMessage> uidTable; // UID->Message hashtable /* An IMAP delimiter is a 7bit US-ASCII character. (except NUL). * We use '\uffff' (a non 7bit character) to indicate that we havent * yet determined what the separator character is. * We use '\u0000' (NUL) to indicate that no separator character * exists, i.e., a flat hierarchy */ static final protected char UNKNOWN_SEPARATOR = '\uffff'; private volatile boolean opened = false; // is this folder opened ? /* This field tracks the state of this folder. If the folder is closed * due to external causes (i.e, not thru the close() method), then * this field will remain false. If the folder is closed thru the * close() method, then this field is set to true. * * If reallyClosed is false, then a FolderClosedException is * generated when a method is invoked on any Messaging object * owned by this folder. If reallyClosed is true, then the * IllegalStateException runtime exception is thrown. */ private boolean reallyClosed = true; /* * The idleState field supports the IDLE command. * Normally when executing an IMAP command we hold the * messageCacheLock and often the folder lock (see above). * While executing the IDLE command we can't hold either * of these locks or it would prevent other threads from * entering Folder methods even far enough to check whether * an IDLE command is in progress. We need to check before * issuing another command so that we can abort the IDLE * command. * * The idleState field is protected by the messageCacheLock. * The RUNNING state is the normal state and means no IDLE * command is in progress. The IDLE state means we've issued * an IDLE command and are reading responses. The ABORTING * state means we've sent the DONE continuation command and * are waiting for the thread running the IDLE command to * break out of its read loop. * * When an IDLE command is in progress, the thread calling * the idle method will be reading from the IMAP connection * while holding neither the folder lock nor the messageCacheLock. * It's obviously critical that no other thread try to send a * command or read from the connection while in this state. * However, other threads can send the DONE continuation * command that will cause the server to break out of the IDLE * loop and send the ending tag response to the IDLE command. * The thread in the idle method that's reading the responses * from the IDLE command will see this ending response and * complete the idle method, setting the idleState field back * to RUNNING, and notifying any threads waiting to use the * connection. * * All uses of the IMAP connection (IMAPProtocol object) must * be done while holding the messageCacheLock and must be * preceeded by a check to make sure an IDLE command is not * running, and abort the IDLE command if necessary. While * waiting for the IDLE command to complete, these other threads * will give up the messageCacheLock, but might still be holding * the folder lock. This check is done by the getProtocol() * method, resulting in a typical usage pattern of: * * synchronized (messageCacheLock) { * IMAPProtocol p = getProtocol(); // may block waiting for IDLE * // ... use protocol * } */ private static final int RUNNING = 0; // not doing IDLE command private static final int IDLE = 1; // IDLE command in effect private static final int ABORTING = 2; // IDLE command aborting private int idleState = RUNNING; private IdleManager idleManager; private volatile int total = -1; // total number of messages in the // message cache private volatile int recent = -1; // number of recent messages private int realTotal = -1; // total number of messages on // the server private long uidvalidity = -1; // UIDValidity private long uidnext = -1; // UIDNext private boolean uidNotSticky = false; // RFC 4315 private volatile long highestmodseq = -1; // RFC 4551 - CONDSTORE private boolean doExpungeNotification = true; // used in expunge handler private Status cachedStatus = null; private long cachedStatusTime = 0; private boolean hasMessageCountListener = false; // optimize notification protected MailLogger logger; private MailLogger connectionPoolLogger; /** * A fetch profile item for fetching headers. * This inner class extends the <code>FetchProfile.Item</code> * class to add new FetchProfile item types, specific to IMAPFolders. * * @see FetchProfile */ public static class FetchProfileItem extends FetchProfile.Item { protected FetchProfileItem(String name) { super(name); } /** * HEADERS is a fetch profile item that can be included in a * <code>FetchProfile</code> during a fetch request to a Folder. * This item indicates that the headers for messages in the specified * range are desired to be prefetched. <p> * * An example of how a client uses this is below: * <blockquote><pre> * * FetchProfile fp = new FetchProfile(); * fp.add(IMAPFolder.FetchProfileItem.HEADERS); * folder.fetch(msgs, fp); * * </pre></blockquote> */ public static final FetchProfileItem HEADERS = new FetchProfileItem("HEADERS"); /** * SIZE is a fetch profile item that can be included in a * <code>FetchProfile</code> during a fetch request to a Folder. * This item indicates that the sizes of the messages in the specified * range are desired to be prefetched. <p> * * SIZE was moved to FetchProfile.Item in JavaMail 1.5. * * @deprecated */ @Deprecated public static final FetchProfileItem SIZE = new FetchProfileItem("SIZE"); /** * MESSAGE is a fetch profile item that can be included in a * <code>FetchProfile</code> during a fetch request to a Folder. * This item indicates that the entire messages (headers and body, * including all "attachments") in the specified * range are desired to be prefetched. Note that the entire message * content is cached in memory while the Folder is open. The cached * message will be parsed locally to return header information and * message content. <p> * * An example of how a client uses this is below: * <blockquote><pre> * * FetchProfile fp = new FetchProfile(); * fp.add(IMAPFolder.FetchProfileItem.MESSAGE); * folder.fetch(msgs, fp); * * </pre></blockquote> * * @since JavaMail 1.5.2 */ public static final FetchProfileItem MESSAGE = new FetchProfileItem("MESSAGE"); /** * INTERNALDATE is a fetch profile item that can be included in a * <code>FetchProfile</code> during a fetch request to a Folder. * This item indicates that the IMAP INTERNALDATE values * (received date) of the messages in the specified * range are desired to be prefetched. <p> * * An example of how a client uses this is below: * <blockquote><pre> * * FetchProfile fp = new FetchProfile(); * fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE); * folder.fetch(msgs, fp); * * </pre></blockquote> * * @since JavaMail 1.5.5 */ public static final FetchProfileItem INTERNALDATE = new FetchProfileItem("INTERNALDATE"); } /** * Constructor used to create a possibly non-existent folder. * * @param fullName fullname of this folder * @param separator the default separator character for this * folder's namespace * @param store the Store * @param isNamespace if this folder represents a namespace */ protected IMAPFolder(String fullName, char separator, IMAPStore store, Boolean isNamespace) { super(store); if (fullName == null) throw new NullPointerException("Folder name is null"); this.fullName = fullName; this.separator = separator; logger = new MailLogger(this.getClass(), "DEBUG IMAP", store.getSession().getDebug(), store.getSession().getDebugOut()); connectionPoolLogger = store.getConnectionPoolLogger(); /* * Work around apparent bug in Exchange. Exchange * will return a name of "Public Folders/" from * LIST "%". * * If name has one separator, and it's at the end, * assume this is a namespace name and treat it * accordingly. Usually this will happen as a result * of the list method, but this also allows getFolder * to work with namespace names. */ this.isNamespace = false; if (separator != UNKNOWN_SEPARATOR && separator != '\0') { int i = this.fullName.indexOf(separator); if (i > 0 && i == this.fullName.length() - 1) { this.fullName = this.fullName.substring(0, i); this.isNamespace = true; } } // if we were given a value, override default chosen above if (isNamespace != null) this.isNamespace = isNamespace.booleanValue(); } /** * Constructor used to create an existing folder. * * @param li the ListInfo for this folder * @param store the store containing this folder */ protected IMAPFolder(ListInfo li, IMAPStore store) { this(li.name, li.separator, store, null); if (li.hasInferiors) type |= HOLDS_FOLDERS; if (li.canOpen) type |= HOLDS_MESSAGES; exists = true; attributes = li.attrs; } /* * Ensure that this folder exists. If 'exists' has been set to true, * we don't attempt to validate it with the server again. Note that * this can result in a possible loss of sync with the server. * ASSERT: Must be called with this folder's synchronization lock held. */ protected void checkExists() throws MessagingException { // If the boolean field 'exists' is false, check with the // server by invoking exists() .. if (!exists && !exists()) throw new FolderNotFoundException( this, fullName + " not found"); } /* * Ensure the folder is closed. * ASSERT: Must be called with this folder's synchronization lock held. */ protected void checkClosed() { if (opened) throw new IllegalStateException( "This operation is not allowed on an open folder" ); } /* * Ensure the folder is open. * ASSERT: Must be called with this folder's synchronization lock held. */ protected void checkOpened() throws FolderClosedException { assert Thread.holdsLock(this); if (!opened) { if (reallyClosed) throw new IllegalStateException( "This operation is not allowed on a closed folder" ); else // Folder was closed "implicitly" throw new FolderClosedException(this, "Lost folder connection to server" ); } } /* * Check that the given message number is within the range * of messages present in this folder. If the message * number is out of range, we ping the server to obtain any * pending new message notifications from the server. */ protected void checkRange(int msgno) throws MessagingException { if (msgno < 1) // message-numbers start at 1 throw new IndexOutOfBoundsException("message number < 1"); if (msgno <= total) return; // Out of range, let's ping the server and see if // the server has more messages for us. synchronized(messageCacheLock) { // Acquire lock try { keepConnectionAlive(false); } catch (ConnectionException cex) { // Oops, lost connection throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } } // Release lock if (msgno > total) // Still out of range ? Throw up ... throw new IndexOutOfBoundsException(msgno + " > " + total); } /* * Check whether the given flags are supported by this server, * and also verify that the folder allows setting flags. */ private void checkFlags(Flags flags) throws MessagingException { assert Thread.holdsLock(this); if (mode != READ_WRITE) throw new IllegalStateException( "Cannot change flags on READ_ONLY folder: " + fullName ); /* if (!availableFlags.contains(flags)) throw new MessagingException( "These flags are not supported by this implementation" ); */ } /** * Get the name of this folder. */ @Override public synchronized String getName() { /* Return the last component of this Folder's full name. * Folder components are delimited by the separator character. */ if (name == null) { try { name = fullName.substring( fullName.lastIndexOf(getSeparator()) + 1 ); } catch (MessagingException mex) { } } return name; } /** * Get the fullname of this folder. */ @Override public String getFullName() { return fullName; } /** * Get this folder's parent. */ @Override public synchronized Folder getParent() throws MessagingException { char c = getSeparator(); int index; if ((index = fullName.lastIndexOf(c)) != -1) return ((IMAPStore)store).newIMAPFolder( fullName.substring(0, index), c); else return new DefaultFolder((IMAPStore)store); } /** * Check whether this folder really exists on the server. */ @Override public synchronized boolean exists() throws MessagingException { // Check whether this folder exists .. ListInfo[] li = null; final String lname; if (isNamespace && separator != '\0') lname = fullName + separator; else lname = fullName; li = (ListInfo[])doCommand(new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { return p.list("", lname); } }); if (li != null) { int i = findName(li, lname); fullName = li[i].name; separator = li[i].separator; int len = fullName.length(); if (separator != '\0' && len > 0 && fullName.charAt(len - 1) == separator) { fullName = fullName.substring(0, len - 1); } type = 0; if (li[i].hasInferiors) type |= HOLDS_FOLDERS; if (li[i].canOpen) type |= HOLDS_MESSAGES; exists = true; attributes = li[i].attrs; } else { exists = opened; attributes = null; } return exists; } /** * Which entry in <code>li</code> matches <code>lname</code>? * If the name contains wildcards, more than one entry may be * returned. */ private int findName(ListInfo[] li, String lname) { int i; // if the name contains a wildcard, there might be more than one for (i = 0; i < li.length; i++) { if (li[i].name.equals(lname)) break; } if (i >= li.length) { // nothing matched exactly // XXX - possibly should fail? But what if server // is case insensitive and returns the preferred // case of the name here? i = 0; // use first one } return i; } /** * List all subfolders matching the specified pattern. */ @Override public Folder[] list(String pattern) throws MessagingException { return doList(pattern, false); } /** * List all subscribed subfolders matching the specified pattern. */ @Override public Folder[] listSubscribed(String pattern) throws MessagingException { return doList(pattern, true); } private synchronized Folder[] doList(final String pattern, final boolean subscribed) throws MessagingException { checkExists(); // insure that this folder does exist. // Why waste a roundtrip to the server? if (attributes != null && !isDirectory()) return new Folder[0]; final char c = getSeparator(); ListInfo[] li = (ListInfo[])doCommandIgnoreFailure( new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { if (subscribed) return p.lsub("", fullName + c + pattern); else return p.list("", fullName + c + pattern); } }); if (li == null) return new Folder[0]; /* * The UW based IMAP4 servers (e.g. SIMS2.0) include * current folder (terminated with the separator), when * the LIST pattern is '%' or '*'. i.e, <LIST "" mail/%> * returns "mail/" as the first LIST response. * * Doesn't make sense to include the current folder in this * case, so we filter it out. Note that I'm assuming that * the offending response is the *first* one, my experiments * with the UW & SIMS2.0 servers indicate that .. */ int start = 0; // Check the first LIST response. if (li.length > 0 && li[0].name.equals(fullName + c)) start = 1; // start from index = 1 IMAPFolder[] folders = new IMAPFolder[li.length - start]; IMAPStore st = (IMAPStore)store; for (int i = start; i < li.length; i++) folders[i-start] = st.newIMAPFolder(li[i]); return folders; } /** * Get the separator character. */ @Override public synchronized char getSeparator() throws MessagingException { if (separator == UNKNOWN_SEPARATOR) { ListInfo[] li = null; li = (ListInfo[])doCommand(new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { // REV1 allows the following LIST format to obtain // the hierarchy delimiter of non-existent folders if (p.isREV1()) // IMAP4rev1 return p.list(fullName, ""); else // IMAP4, note that this folder must exist for this // to work :( return p.list("", fullName); } }); if (li != null) separator = li[0].separator; else separator = '/'; // punt ! } return separator; } /** * Get the type of this folder. */ @Override public synchronized int getType() throws MessagingException { if (opened) { // never throw FolderNotFoundException if folder is open if (attributes == null) exists(); // try to fetch attributes } else { checkExists(); } return type; } /** * Check whether this folder is subscribed. */ @Override public synchronized boolean isSubscribed() { ListInfo[] li = null; final String lname; if (isNamespace && separator != '\0') lname = fullName + separator; else lname = fullName; try { li = (ListInfo[])doProtocolCommand(new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { return p.lsub("", lname); } }); } catch (ProtocolException pex) { } if (li != null) { int i = findName(li, lname); return li[i].canOpen; } else return false; } /** * Subscribe/Unsubscribe this folder. */ @Override public synchronized void setSubscribed(final boolean subscribe) throws MessagingException { doCommandIgnoreFailure(new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { if (subscribe) p.subscribe(fullName); else p.unsubscribe(fullName); return null; } }); } /** * Create this folder, with the specified type. */ @Override public synchronized boolean create(final int type) throws MessagingException { char c = 0; if ((type & HOLDS_MESSAGES) == 0) // only holds folders c = getSeparator(); final char sep = c; Object ret = doCommandIgnoreFailure(new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { if ((type & HOLDS_MESSAGES) == 0) // only holds folders p.create(fullName + sep); else { p.create(fullName); // Certain IMAP servers do not allow creation of folders // that can contain messages *and* subfolders. So, if we // were asked to create such a folder, we should verify // that we could indeed do so. if ((type & HOLDS_FOLDERS) != 0) { // we want to hold subfolders and messages. Check // whether we could create such a folder. ListInfo[] li = p.list("", fullName); if (li != null && !li[0].hasInferiors) { // Hmm ..the new folder // doesn't support Inferiors ? Fail p.delete(fullName); throw new ProtocolException("Unsupported type"); } } } return Boolean.TRUE; } }); if (ret == null) return false; // CREATE failure, maybe this // folder already exists ? // exists = true; // this.type = type; boolean retb = exists(); // set exists, type, and attributes if (retb) // Notify listeners on self and our Store notifyFolderListeners(FolderEvent.CREATED); return retb; } /** * Check whether this folder has new messages. */ @Override public synchronized boolean hasNewMessages() throws MessagingException { synchronized (messageCacheLock) { if (opened) { // If we are open, we already have this information // Folder is open, make sure information is up to date // tickle the folder and store connections. try { keepConnectionAlive(true); } catch (ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } return recent > 0 ? true : false; } } // First, the cheap way - use LIST and look for the \Marked // or \Unmarked tag ListInfo[] li = null; final String lname; if (isNamespace && separator != '\0') lname = fullName + separator; else lname = fullName; li = (ListInfo[])doCommandIgnoreFailure(new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { return p.list("", lname); } }); // if folder doesn't exist, throw exception if (li == null) throw new FolderNotFoundException(this, fullName + " not found"); int i = findName(li, lname); if (li[i].changeState == ListInfo.CHANGED) return true; else if (li[i].changeState == ListInfo.UNCHANGED) return false; // LIST didn't work. Try the hard way, using STATUS try { Status status = getStatus(); if (status.recent > 0) return true; else return false; } catch (BadCommandException bex) { // Probably doesn't support STATUS, tough luck. return false; } catch (ConnectionException cex) { throw new StoreClosedException(store, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } } /** * Get the named subfolder. */ @Override public synchronized Folder getFolder(String name) throws MessagingException { // If we know that this folder is *not* a directory, don't // send the request to the server at all ... if (attributes != null && !isDirectory()) throw new MessagingException("Cannot contain subfolders"); char c = getSeparator(); return ((IMAPStore)store).newIMAPFolder(fullName + c + name, c); } /** * Delete this folder. */ @Override public synchronized boolean delete(boolean recurse) throws MessagingException { checkClosed(); // insure that this folder is closed. if (recurse) { // Delete all subfolders. Folder[] f = list(); for (int i = 0; i < f.length; i++) f[i].delete(recurse); // ignore intermediate failures } // Attempt to delete this folder Object ret = doCommandIgnoreFailure(new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { p.delete(fullName); return Boolean.TRUE; } }); if (ret == null) // Non-existent folder/No permission ?? return false; // DELETE succeeded. exists = false; attributes = null; // Notify listeners on self and our Store notifyFolderListeners(FolderEvent.DELETED); return true; } /** * Rename this folder. */ @Override public synchronized boolean renameTo(final Folder f) throws MessagingException { checkClosed(); // insure that we are closed. checkExists(); if (f.getStore() != store) throw new MessagingException("Can't rename across Stores"); Object ret = doCommandIgnoreFailure(new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { p.rename(fullName, f.getFullName()); return Boolean.TRUE; } }); if (ret == null) return false; exists = false; attributes = null; notifyFolderRenamedListeners(f); return true; } /** * Open this folder in the given mode. */ @Override public synchronized void open(int mode) throws MessagingException { open(mode, null); } /** * Open this folder in the given mode, with the given * resynchronization data. * * @param mode the open mode (Folder.READ_WRITE or Folder.READ_ONLY) * @param rd the ResyncData instance * @return a List of MailEvent instances, or null if none * @exception MessagingException if the open fails * @since JavaMail 1.5.1 */ public synchronized List<MailEvent> open(int mode, ResyncData rd) throws MessagingException { checkClosed(); // insure that we are not already open MailboxInfo mi = null; // Request store for our own protocol connection. protocol = ((IMAPStore)store).getProtocol(this); List<MailEvent> openEvents = null; synchronized(messageCacheLock) { // Acquire messageCacheLock /* * Add response handler right away so we get any alerts or * notifications that occur during the SELECT or EXAMINE. * Have to be sure to remove it if we fail to open the * folder. */ protocol.addResponseHandler(this); try { /* * Enable QRESYNC or CONDSTORE if needed and not enabled. * QRESYNC implies CONDSTORE, but servers that support * QRESYNC are not required to support just CONDSTORE * per RFC 5162. */ if (rd != null) { if (rd == ResyncData.CONDSTORE) { if (!protocol.isEnabled("CONDSTORE") && !protocol.isEnabled("QRESYNC")) { if (protocol.hasCapability("CONDSTORE")) protocol.enable("CONDSTORE"); else protocol.enable("QRESYNC"); } } else { if (!protocol.isEnabled("QRESYNC")) protocol.enable("QRESYNC"); } } if (mode == READ_ONLY) mi = protocol.examine(fullName, rd); else mi = protocol.select(fullName, rd); } catch (CommandFailedException cex) { /* * Handle SELECT or EXAMINE failure. * Try to figure out why the operation failed so we can * report a more reasonable exception. * * Will use our existing protocol object. */ try { checkExists(); // throw exception if folder doesn't exist if ((type & HOLDS_MESSAGES) == 0) throw new MessagingException( "folder cannot contain messages"); throw new MessagingException(cex.getMessage(), cex); } finally { // folder not open, don't keep this information exists = false; attributes = null; type = 0; // connection still good, return it releaseProtocol(true); } // NOTREACHED } catch (ProtocolException pex) { // got a BAD or a BYE; connection may be bad, close it try { throw logoutAndThrow(pex.getMessage(), pex); } finally { releaseProtocol(false); } } if (mi.mode != mode) { if (mode == READ_WRITE && mi.mode == READ_ONLY && ((IMAPStore)store).allowReadOnlySelect()) { ; // all ok, allow it } else { // otherwise, it's an error ReadOnlyFolderException ife = new ReadOnlyFolderException( this, "Cannot open in desired mode"); throw cleanupAndThrow(ife); } } // Initialize stuff. opened = true; reallyClosed = false; this.mode = mi.mode; availableFlags = mi.availableFlags; permanentFlags = mi.permanentFlags; total = realTotal = mi.total; recent = mi.recent; uidvalidity = mi.uidvalidity; uidnext = mi.uidnext; uidNotSticky = mi.uidNotSticky; highestmodseq = mi.highestmodseq; // Create the message cache of appropriate size messageCache = new MessageCache(this, (IMAPStore)store, total); // process saved responses and return corresponding events if (mi.responses != null) { openEvents = new ArrayList<>(); for (IMAPResponse ir : mi.responses) { if (ir.keyEquals("VANISHED")) { // "VANISHED" SP ["(EARLIER)"] SP known-uids String[] s = ir.readAtomStringList(); // check that it really is "EARLIER" if (s == null || s.length != 1 || !s[0].equalsIgnoreCase("EARLIER")) continue; // it's not, what to do with it here? String uids = ir.readAtom(); UIDSet[] uidset = UIDSet.parseUIDSets(uids); long[] luid = UIDSet.toArray(uidset, uidnext); if (luid != null && luid.length > 0) openEvents.add( new MessageVanishedEvent(this, luid)); } else if (ir.keyEquals("FETCH")) { assert ir instanceof FetchResponse : "!ir instanceof FetchResponse"; Message msg = processFetchResponse((FetchResponse)ir); if (msg != null) openEvents.add(new MessageChangedEvent(this, MessageChangedEvent.FLAGS_CHANGED, msg)); } } } } // Release lock exists = true; // if we opened it, it must exist attributes = null; // but we don't yet know its attributes type = HOLDS_MESSAGES; // lacking more info, we know at least this much // notify listeners notifyConnectionListeners(ConnectionEvent.OPENED); return openEvents; } private MessagingException cleanupAndThrow(MessagingException ife) { try { try { // close mailbox and return connection protocol.close(); releaseProtocol(true); } catch (ProtocolException pex) { // something went wrong, close connection try { addSuppressed(ife, logoutAndThrow(pex.getMessage(), pex)); } finally { releaseProtocol(false); } } } catch (Throwable thr) { addSuppressed(ife, thr); } return ife; } private MessagingException logoutAndThrow(String why, ProtocolException t) { MessagingException ife = new MessagingException(why, t); try { protocol.logout(); } catch (Throwable thr) { addSuppressed(ife, thr); } return ife; } private void addSuppressed(Throwable ife, Throwable thr) { if (isRecoverable(thr)) { ife.addSuppressed(thr); } else { thr.addSuppressed(ife); if (thr instanceof Error) { throw (Error) thr; } if (thr instanceof RuntimeException) { throw (RuntimeException) thr; } throw new RuntimeException("unexpected exception", thr); } } private boolean isRecoverable(Throwable t) { return (t instanceof Exception) || (t instanceof LinkageError); } /** * Prefetch attributes, based on the given FetchProfile. */ @Override public synchronized void fetch(Message[] msgs, FetchProfile fp) throws MessagingException { // cache this information in case connection is closed and // protocol is set to null boolean isRev1; FetchItem[] fitems; synchronized (messageCacheLock) { checkOpened(); isRev1 = protocol.isREV1(); fitems = protocol.getFetchItems(); } StringBuilder command = new StringBuilder(); boolean first = true; boolean allHeaders = false; if (fp.contains(FetchProfile.Item.ENVELOPE)) { command.append(getEnvelopeCommand()); first = false; } if (fp.contains(FetchProfile.Item.FLAGS)) { command.append(first ? "FLAGS" : " FLAGS"); first = false; } if (fp.contains(FetchProfile.Item.CONTENT_INFO)) { command.append(first ? "BODYSTRUCTURE" : " BODYSTRUCTURE"); first = false; } if (fp.contains(UIDFolder.FetchProfileItem.UID)) { command.append(first ? "UID" : " UID"); first = false; } if (fp.contains(IMAPFolder.FetchProfileItem.HEADERS)) { allHeaders = true; if (isRev1) command.append(first ? "BODY.PEEK[HEADER]" : " BODY.PEEK[HEADER]"); else command.append(first ? "RFC822.HEADER" : " RFC822.HEADER"); first = false; } if (fp.contains(IMAPFolder.FetchProfileItem.MESSAGE)) { allHeaders = true; if (isRev1) command.append(first ? "BODY.PEEK[]" : " BODY.PEEK[]"); else command.append(first ? "RFC822" : " RFC822"); first = false; } if (fp.contains(FetchProfile.Item.SIZE) || fp.contains(IMAPFolder.FetchProfileItem.SIZE)) { command.append(first ? "RFC822.SIZE" : " RFC822.SIZE"); first = false; } if (fp.contains(IMAPFolder.FetchProfileItem.INTERNALDATE)) { command.append(first ? "INTERNALDATE" : " INTERNALDATE"); first = false; } // if we're not fetching all headers, fetch individual headers String[] hdrs = null; if (!allHeaders) { hdrs = fp.getHeaderNames(); if (hdrs.length > 0) { if (!first) command.append(" "); command.append(createHeaderCommand(hdrs, isRev1)); } } /* * Add any additional extension fetch items. */ for (int i = 0; i < fitems.length; i++) { if (fp.contains(fitems[i].getFetchProfileItem())) { if (command.length() != 0) command.append(" "); command.append(fitems[i].getName()); } } Utility.Condition condition = new IMAPMessage.FetchProfileCondition(fp, fitems); // Acquire the Folder's MessageCacheLock. synchronized (messageCacheLock) { // check again to make sure folder is still open checkOpened(); // Apply the test, and get the sequence-number set for // the messages that need to be prefetched. MessageSet[] msgsets = Utility.toMessageSetSorted(msgs, condition); if (msgsets == null) // We already have what we need. return; Response[] r = null; // to collect non-FETCH responses & unsolicited FETCH FLAG responses List<Response> v = new ArrayList<>(); try { r = getProtocol().fetch(msgsets, command.toString()); } catch (ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (CommandFailedException cfx) { // Ignore these, as per RFC 2180 } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } if (r == null) return; for (int i = 0; i < r.length; i++) { if (r[i] == null) continue; if (!(r[i] instanceof FetchResponse)) { v.add(r[i]); // Unsolicited Non-FETCH response continue; } // Got a FetchResponse. FetchResponse f = (FetchResponse)r[i]; // Get the corresponding message. IMAPMessage msg = getMessageBySeqNumber(f.getNumber()); int count = f.getItemCount(); boolean unsolicitedFlags = false; for (int j = 0; j < count; j++) { Item item = f.getItem(j); // Check for the FLAGS item if (item instanceof Flags && (!fp.contains(FetchProfile.Item.FLAGS) || msg == null)) { // Ok, Unsolicited FLAGS update. unsolicitedFlags = true; } else if (msg != null) msg.handleFetchItem(item, hdrs, allHeaders); } if (msg != null) msg.handleExtensionFetchItems(f.getExtensionItems()); // If this response contains any unsolicited FLAGS // add it to the unsolicited response vector if (unsolicitedFlags) v.add(f); } // Dispatch any unsolicited responses if (!v.isEmpty()) { Response[] responses = new Response[v.size()]; v.toArray(responses); handleResponses(responses); } } // Release messageCacheLock } /** * Return the IMAP FETCH items to request in order to load * all the "envelope" data. Subclasses can override this * method to fetch more data when FetchProfile.Item.ENVELOPE * is requested. * * @return the IMAP FETCH items to request * @since JavaMail 1.4.6 */ protected String getEnvelopeCommand() { return IMAPMessage.EnvelopeCmd; } /** * Create a new IMAPMessage object to represent the given message number. * Subclasses of IMAPFolder may override this method to create a * subclass of IMAPMessage. * * @param msgnum the message sequence number * @return the new IMAPMessage object * @since JavaMail 1.4.6 */ protected IMAPMessage newIMAPMessage(int msgnum) { return new IMAPMessage(this, msgnum); } /** * Create the appropriate IMAP FETCH command items to fetch the * requested headers. */ private String createHeaderCommand(String[] hdrs, boolean isRev1) { StringBuilder sb; if (isRev1) sb = new StringBuilder("BODY.PEEK[HEADER.FIELDS ("); else sb = new StringBuilder("RFC822.HEADER.LINES ("); for (int i = 0; i < hdrs.length; i++) { if (i > 0) sb.append(" "); sb.append(hdrs[i]); } if (isRev1) sb.append(")]"); else sb.append(")"); return sb.toString(); } /** * Set the specified flags for the given array of messages. */ @Override public synchronized void setFlags(Message[] msgs, Flags flag, boolean value) throws MessagingException { checkOpened(); checkFlags(flag); // validate flags if (msgs.length == 0) // boundary condition return; synchronized(messageCacheLock) { try { IMAPProtocol p = getProtocol(); MessageSet[] ms = Utility.toMessageSetSorted(msgs, null); if (ms == null) throw new MessageRemovedException( "Messages have been removed"); p.storeFlags(ms, flag, value); } catch (ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } } } /** * Set the specified flags for the given range of message numbers. */ @Override public synchronized void setFlags(int start, int end, Flags flag, boolean value) throws MessagingException { checkOpened(); Message[] msgs = new Message[end - start + 1]; int i = 0; for (int n = start; n <= end; n++) msgs[i++] = getMessage(n); setFlags(msgs, flag, value); } /** * Set the specified flags for the given array of message numbers. */ @Override public synchronized void setFlags(int[] msgnums, Flags flag, boolean value) throws MessagingException { checkOpened(); Message[] msgs = new Message[msgnums.length]; for (int i = 0; i < msgnums.length; i++) msgs[i] = getMessage(msgnums[i]); setFlags(msgs, flag, value); } /** * Close this folder. */ @Override public synchronized void close(boolean expunge) throws MessagingException { close(expunge, false); } /** * Close this folder without waiting for the server. * * @exception MessagingException for failures */ public synchronized void forceClose() throws MessagingException { close(false, true); } /* * Common close method. */ private void close(boolean expunge, boolean force) throws MessagingException { assert Thread.holdsLock(this); synchronized(messageCacheLock) { /* * If we already know we're closed, this is illegal. * Can't use checkOpened() because if we were forcibly * closed asynchronously we just want to complete the * closing here. */ if (!opened && reallyClosed) throw new IllegalStateException( "This operation is not allowed on a closed folder" ); reallyClosed = true; // Ok, lets reset // Maybe this folder is already closed, or maybe another // thread which had the messageCacheLock earlier, found // that our server connection is dead and cleaned up // everything .. if (!opened) return; boolean reuseProtocol = true; try { waitIfIdle(); if (force) { logger.log(Level.FINE, "forcing folder {0} to close", fullName); if (protocol != null) protocol.disconnect(); } else if (((IMAPStore)store).isConnectionPoolFull()) { // If the connection pool is full, logout the connection logger.fine( "pool is full, not adding an Authenticated connection"); // If the expunge flag is set, close the folder first. if (expunge && protocol != null) protocol.close(); if (protocol != null) protocol.logout(); } else { // If the expunge flag is set or we're open read-only we // can just close the folder, otherwise open it read-only // before closing, or unselect it if supported. if (!expunge && mode == READ_WRITE) { try { if (protocol != null && protocol.hasCapability("UNSELECT")) protocol.unselect(); else { // Unselect isn't supported so we need to // select a folder to cause this one to be // deselected without expunging messages. // We try to do that by reopening the current // folder read-only. If the current folder // was renamed out from under us, the EXAMINE // might fail, but that's ok because it still // leaves us with the folder deselected. if (protocol != null) { boolean selected = true; try { protocol.examine(fullName); // success, folder still selected } catch (CommandFailedException ex) { // EXAMINE failed, folder is no // longer selected selected = false; } if (selected && protocol != null) protocol.close(); } } } catch (ProtocolException pex2) { reuseProtocol = false; // something went wrong } } else { if (protocol != null) protocol.close(); } } } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } finally { // cleanup if we haven't already if (opened) cleanup(reuseProtocol); } } } // NOTE: this method can currently be invoked from close() or // from handleResponses(). Both invocations are conditional, // based on the "opened" flag, so we are sure that multiple // Connection.CLOSED events are not generated. Also both // invocations are from within messageCacheLock-ed areas. private void cleanup(boolean returnToPool) { assert Thread.holdsLock(messageCacheLock); releaseProtocol(returnToPool); messageCache = null; uidTable = null; exists = false; // to force a recheck in exists(). attributes = null; opened = false; idleState = RUNNING; // just in case messageCacheLock.notifyAll(); // wake up anyone waiting notifyConnectionListeners(ConnectionEvent.CLOSED); } /** * Check whether this connection is really open. */ @Override public synchronized boolean isOpen() { synchronized(messageCacheLock) { // Probe the connection to make sure its really open. if (opened) { try { keepConnectionAlive(false); } catch (ProtocolException pex) { } } } return opened; } /** * Return the permanent flags supported by the server. */ @Override public synchronized Flags getPermanentFlags() { if (permanentFlags == null) return null; return (Flags)(permanentFlags.clone()); } /** * Get the total message count. */ @Override public synchronized int getMessageCount() throws MessagingException { synchronized (messageCacheLock) { if (opened) { // Folder is open, we know what the total message count is .. // tickle the folder and store connections. try { keepConnectionAlive(true); return total; } catch (ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } } } // If this folder is not yet open, we use STATUS to // get the total message count checkExists(); try { Status status = getStatus(); return status.total; } catch (BadCommandException bex) { // doesn't support STATUS, probably vanilla IMAP4 .. // lets try EXAMINE IMAPProtocol p = null; try { p = getStoreProtocol(); // XXX MailboxInfo minfo = p.examine(fullName); p.close(); return minfo.total; } catch (ProtocolException pex) { // Give up. throw new MessagingException(pex.getMessage(), pex); } finally { releaseStoreProtocol(p); } } catch (ConnectionException cex) { throw new StoreClosedException(store, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } } /** * Get the new message count. */ @Override public synchronized int getNewMessageCount() throws MessagingException { synchronized (messageCacheLock) { if (opened) { // Folder is open, we know what the new message count is .. // tickle the folder and store connections. try { keepConnectionAlive(true); return recent; } catch (ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } } } // If this folder is not yet open, we use STATUS to // get the new message count checkExists(); try { Status status = getStatus(); return status.recent; } catch (BadCommandException bex) { // doesn't support STATUS, probably vanilla IMAP4 .. // lets try EXAMINE IMAPProtocol p = null; try { p = getStoreProtocol(); // XXX MailboxInfo minfo = p.examine(fullName); p.close(); return minfo.recent; } catch (ProtocolException pex) { // Give up. throw new MessagingException(pex.getMessage(), pex); } finally { releaseStoreProtocol(p); } } catch (ConnectionException cex) { throw new StoreClosedException(store, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } } /** * Get the unread message count. */ @Override public synchronized int getUnreadMessageCount() throws MessagingException { if (!opened) { checkExists(); // If this folder is not yet open, we use STATUS to // get the unseen message count try { Status status = getStatus(); return status.unseen; } catch (BadCommandException bex) { // doesn't support STATUS, probably vanilla IMAP4 .. // Could EXAMINE, SEARCH for UNREAD messages and // return the count .. bah, not worth it. return -1; } catch (ConnectionException cex) { throw new StoreClosedException(store, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } } // if opened, issue server-side search for messages that do // *not* have the SEEN flag. Flags f = new Flags(); f.add(Flags.Flag.SEEN); try { synchronized(messageCacheLock) { int[] matches = getProtocol().search(new FlagTerm(f, false)); return matches.length; // NOTE: 'matches' is never null } } catch (ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { // Shouldn't happen throw new MessagingException(pex.getMessage(), pex); } } /** * Get the deleted message count. */ @Override public synchronized int getDeletedMessageCount() throws MessagingException { if (!opened) { checkExists(); // no way to do this on closed folders return -1; } // if opened, issue server-side search for messages that do // have the DELETED flag. Flags f = new Flags(); f.add(Flags.Flag.DELETED); try { synchronized(messageCacheLock) { int[] matches = getProtocol().search(new FlagTerm(f, true)); return matches.length; // NOTE: 'matches' is never null } } catch (ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { // Shouldn't happen throw new MessagingException(pex.getMessage(), pex); } } /* * Get results of STATUS command for this folder, checking cache first. * ASSERT: Must be called with this folder's synchronization lock held. * ASSERT: The folder must be closed. */ private Status getStatus() throws ProtocolException { int statusCacheTimeout = ((IMAPStore)store).getStatusCacheTimeout(); // if allowed to cache and our cache is still valid, return it if (statusCacheTimeout > 0 && cachedStatus != null && System.currentTimeMillis() - cachedStatusTime < statusCacheTimeout) return cachedStatus; IMAPProtocol p = null; try { p = getStoreProtocol(); // XXX Status s = p.status(fullName, null); // if allowed to cache, do so if (statusCacheTimeout > 0) { cachedStatus = s; cachedStatusTime = System.currentTimeMillis(); } return s; } finally { releaseStoreProtocol(p); } } /** * Get the specified message. */ @Override public synchronized Message getMessage(int msgnum) throws MessagingException { checkOpened(); checkRange(msgnum); return messageCache.getMessage(msgnum); } /** * {@inheritDoc} */ @Override public synchronized Message[] getMessages() throws MessagingException { /* * Need to override Folder method to throw FolderClosedException * instead of IllegalStateException if not really closed. */ checkOpened(); int total = getMessageCount(); Message[] msgs = new Message[total]; for (int i = 1; i <= total; i++) msgs[i - 1] = messageCache.getMessage(i); return msgs; } /** * Append the given messages into this folder. */ @Override public synchronized void appendMessages(Message[] msgs) throws MessagingException { checkExists(); // verify that self exists // XXX - have to verify that messages are in a different // store (if any) than target folder, otherwise could // deadlock trying to fetch messages on the same connection // we're using for the append. int maxsize = ((IMAPStore)store).getAppendBufferSize(); for (int i = 0; i < msgs.length; i++) { final Message m = msgs[i]; Date d = m.getReceivedDate(); // retain dates if (d == null) d = m.getSentDate(); final Date dd = d; final Flags f = m.getFlags(); final MessageLiteral mos; try { // if we know the message is too big, don't buffer any of it mos = new MessageLiteral(m, m.getSize() > maxsize ? 0 : maxsize); } catch (IOException ex) { throw new MessagingException( "IOException while appending messages", ex); } catch (MessageRemovedException mrex) { continue; // just skip this expunged message } doCommand(new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { p.append(fullName, f, dd, mos); return null; } }); } } /** * Append the given messages into this folder. * Return array of AppendUID objects containing * UIDs of these messages in the destination folder. * Each element of the returned array corresponds to * an element of the <code>msgs</code> array. A null * element means the server didn't return UID information * for the appended message. <p> * * Depends on the APPENDUID response code defined by the * UIDPLUS extension - * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>. * * @param msgs the messages to append * @return array of AppendUID objects * @exception MessagingException for failures * @since JavaMail 1.4 */ public synchronized AppendUID[] appendUIDMessages(Message[] msgs) throws MessagingException { checkExists(); // verify that self exists // XXX - have to verify that messages are in a different // store (if any) than target folder, otherwise could // deadlock trying to fetch messages on the same connection // we're using for the append. int maxsize = ((IMAPStore)store).getAppendBufferSize(); AppendUID[] uids = new AppendUID[msgs.length]; for (int i = 0; i < msgs.length; i++) { final Message m = msgs[i]; final MessageLiteral mos; try { // if we know the message is too big, don't buffer any of it mos = new MessageLiteral(m, m.getSize() > maxsize ? 0 : maxsize); } catch (IOException ex) { throw new MessagingException( "IOException while appending messages", ex); } catch (MessageRemovedException mrex) { continue; // just skip this expunged message } Date d = m.getReceivedDate(); // retain dates if (d == null) d = m.getSentDate(); final Date dd = d; final Flags f = m.getFlags(); AppendUID auid = (AppendUID)doCommand(new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { return p.appenduid(fullName, f, dd, mos); } }); uids[i] = auid; } return uids; } /** * Append the given messages into this folder. * Return array of Message objects representing * the messages in the destination folder. Note * that the folder must be open. * Each element of the returned array corresponds to * an element of the <code>msgs</code> array. A null * element means the server didn't return UID information * for the appended message. <p> * * Depends on the APPENDUID response code defined by the * UIDPLUS extension - * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>. * * @param msgs the messages to add * @return the messages in this folder * @exception MessagingException for failures * @since JavaMail 1.4 */ public synchronized Message[] addMessages(Message[] msgs) throws MessagingException { checkOpened(); Message[] rmsgs = new MimeMessage[msgs.length]; AppendUID[] uids = appendUIDMessages(msgs); for (int i = 0; i < uids.length; i++) { AppendUID auid = uids[i]; if (auid != null) { if (auid.uidvalidity == uidvalidity) { try { rmsgs[i] = getMessageByUID(auid.uid); } catch (MessagingException mex) { // ignore errors at this stage } } } } return rmsgs; } /** * Copy the specified messages from this folder, to the * specified destination. */ @Override public synchronized void copyMessages(Message[] msgs, Folder folder) throws MessagingException { copymoveMessages(msgs, folder, false); } /** * Copy the specified messages from this folder, to the * specified destination. * Return array of AppendUID objects containing * UIDs of these messages in the destination folder. * Each element of the returned array corresponds to * an element of the <code>msgs</code> array. A null * element means the server didn't return UID information * for the copied message. <p> * * Depends on the COPYUID response code defined by the * UIDPLUS extension - * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>. * * @param msgs the messages to copy * @param folder the folder to copy the messages to * @return array of AppendUID objects * @exception MessagingException for failures * @since JavaMail 1.5.1 */ public synchronized AppendUID[] copyUIDMessages(Message[] msgs, Folder folder) throws MessagingException { return copymoveUIDMessages(msgs, folder, false); } /** * Move the specified messages from this folder, to the * specified destination. * * Depends on the MOVE extension * (<A HREF="http://www.ietf.org/rfc/rfc6851.txt">RFC 6851</A>). * * @param msgs the messages to move * @param folder the folder to move the messages to * @exception MessagingException for failures * * @since JavaMail 1.5.4 */ public synchronized void moveMessages(Message[] msgs, Folder folder) throws MessagingException { copymoveMessages(msgs, folder, true); } /** * Move the specified messages from this folder, to the * specified destination. * Return array of AppendUID objects containing * UIDs of these messages in the destination folder. * Each element of the returned array corresponds to * an element of the <code>msgs</code> array. A null * element means the server didn't return UID information * for the moved message. <p> * * Depends on the MOVE extension * (<A HREF="http://www.ietf.org/rfc/rfc6851.txt">RFC 6851</A>) * and the COPYUID response code defined by the * UIDPLUS extension * (<A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>). * * @param msgs the messages to move * @param folder the folder to move the messages to * @return array of AppendUID objects * @exception MessagingException for failures * @since JavaMail 1.5.4 */ public synchronized AppendUID[] moveUIDMessages(Message[] msgs, Folder folder) throws MessagingException { return copymoveUIDMessages(msgs, folder, true); } /** * Copy or move the specified messages from this folder, to the * specified destination. * * @since JavaMail 1.5.4 */ private synchronized void copymoveMessages(Message[] msgs, Folder folder, boolean move) throws MessagingException { checkOpened(); if (msgs.length == 0) // boundary condition return; // If the destination belongs to our same store, optimize if (folder.getStore() == store) { synchronized(messageCacheLock) { try { IMAPProtocol p = getProtocol(); MessageSet[] ms = Utility.toMessageSet(msgs, null); if (ms == null) throw new MessageRemovedException( "Messages have been removed"); if (move) p.move(ms, folder.getFullName()); else p.copy(ms, folder.getFullName()); } catch (CommandFailedException cfx) { if (cfx.getMessage().indexOf("TRYCREATE") != -1) throw new FolderNotFoundException( folder, folder.getFullName() + " does not exist" ); else throw new MessagingException(cfx.getMessage(), cfx); } catch (ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } } } else // destination is a different store. if (move) throw new MessagingException( "Move between stores not supported"); else super.copyMessages(msgs, folder); } /** * Copy or move the specified messages from this folder, to the * specified destination. * Return array of AppendUID objects containing * UIDs of these messages in the destination folder. * Each element of the returned array corresponds to * an element of the <code>msgs</code> array. A null * element means the server didn't return UID information * for the copied message. <p> * * Depends on the COPYUID response code defined by the * UIDPLUS extension - * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>. * Move depends on the MOVE extension - * <A HREF="http://www.ietf.org/rfc/rfc6851.txt">RFC 6851</A>. * * @param msgs the messages to copy * @param folder the folder to copy the messages to * @param move move instead of copy? * @return array of AppendUID objects * @exception MessagingException for failures * @since JavaMail 1.5.4 */ private synchronized AppendUID[] copymoveUIDMessages(Message[] msgs, Folder folder, boolean move) throws MessagingException { checkOpened(); if (msgs.length == 0) // boundary condition return null; // the destination must belong to our same store if (folder.getStore() != store) // destination is a different store. throw new MessagingException( move ? "can't moveUIDMessages to a different store" : "can't copyUIDMessages to a different store"); // call fetch to make sure we have all the UIDs // necessary to interpret the COPYUID response FetchProfile fp = new FetchProfile(); fp.add(UIDFolder.FetchProfileItem.UID); fetch(msgs, fp); // XXX - could pipeline the FETCH with the COPY/MOVE below synchronized (messageCacheLock) { try { IMAPProtocol p = getProtocol(); // XXX - messages have to be from this Folder, who checks? MessageSet[] ms = Utility.toMessageSet(msgs, null); if (ms == null) throw new MessageRemovedException( "Messages have been removed"); CopyUID cuid; if (move) cuid = p.moveuid(ms, folder.getFullName()); else cuid = p.copyuid(ms, folder.getFullName()); /* * Correlate source UIDs with destination UIDs. * This won't be time or space efficient if there's * a lot of messages. * * In order to make sense of the returned UIDs, we need * the UIDs for every one of the original messages. * We fetch them above, to make sure we have them. * This is critical for MOVE since after the MOVE the * messages are gone/expunged. * * Assume the common case is that the messages are * in order by UID. Map the returned source * UIDs to their corresponding Message objects. * Step through the msgs array looking for the * Message object in the returned source message * list. Most commonly the source message (UID) * for the Nth original message will be in the Nth * position in the returned source message (UID) * list. Thus, the destination UID is in the Nth * position in the returned destination UID list. * But if the source message isn't where expected, * we have to search the entire source message * list, starting from where we expect it and * wrapping around until we've searched it all. * (Gmail will often return the lists in an unexpected order.) * * A possible optimization: * If the number of UIDs returned is the same as the * number of messages being copied/moved, we could * sort the source messages by message number, sort * the source and destination parallel arrays by source * UID, and the resulting message and destination UID * arrays will correspond. * * If the returned UID array size is different, some * message was expunged while we were trying to copy/move it. * This should be rare but would mean falling back to the * general algorithm. */ long[] srcuids = UIDSet.toArray(cuid.src); long[] dstuids = UIDSet.toArray(cuid.dst); // map source UIDs to Message objects // XXX - could inline/optimize this Message[] srcmsgs = getMessagesByUID(srcuids); AppendUID[] result = new AppendUID[msgs.length]; for (int i = 0; i < msgs.length; i++) { int j = i; do { if (msgs[i] == srcmsgs[j]) { result[i] = new AppendUID( cuid.uidvalidity, dstuids[j]); break; } j++; if (j >= srcmsgs.length) j = 0; } while (j != i); } return result; } catch (CommandFailedException cfx) { if (cfx.getMessage().indexOf("TRYCREATE") != -1) throw new FolderNotFoundException( folder, folder.getFullName() + " does not exist" ); else throw new MessagingException(cfx.getMessage(), cfx); } catch (ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } } } /** * Expunge all messages marked as DELETED. */ @Override public synchronized Message[] expunge() throws MessagingException { return expunge(null); } /** * Expunge the indicated messages, which must have been marked as DELETED. * * Depends on the UIDPLUS extension - * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>. * * @param msgs the messages to expunge * @return the expunged messages * @exception MessagingException for failures */ public synchronized Message[] expunge(Message[] msgs) throws MessagingException { checkOpened(); if (msgs != null) { // call fetch to make sure we have all the UIDs FetchProfile fp = new FetchProfile(); fp.add(UIDFolder.FetchProfileItem.UID); fetch(msgs, fp); } IMAPMessage[] rmsgs; synchronized(messageCacheLock) { doExpungeNotification = false; // We do this ourselves later try { IMAPProtocol p = getProtocol(); if (msgs != null) p.uidexpunge(Utility.toUIDSet(msgs)); else p.expunge(); } catch (CommandFailedException cfx) { // expunge not allowed, perhaps due to a permission problem? if (mode != READ_WRITE) throw new IllegalStateException( "Cannot expunge READ_ONLY folder: " + fullName); else throw new MessagingException(cfx.getMessage(), cfx); } catch (ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { // Bad bad server .. throw new MessagingException(pex.getMessage(), pex); } finally { doExpungeNotification = true; } // Cleanup expunged messages and sync messageCache with reality. if (msgs != null) rmsgs = messageCache.removeExpungedMessages(msgs); else rmsgs = messageCache.removeExpungedMessages(); if (uidTable != null) { for (int i = 0; i < rmsgs.length; i++) { IMAPMessage m = rmsgs[i]; /* remove this message from the UIDTable */ long uid = m.getUID(); if (uid != -1) uidTable.remove(Long.valueOf(uid)); } } // Update 'total' total = messageCache.size(); } // Notify listeners. This time its for real, guys. if (rmsgs.length > 0) notifyMessageRemovedListeners(true, rmsgs); return rmsgs; } /** * Search whole folder for messages matching the given term. * If the property <code>mail.imap.throwsearchexception</code> is true, * and the search term is too complex for the IMAP protocol, * SearchException is thrown. Otherwise, if the search term is too * complex, <code>super.search</code> is called to do the search on * the client. * * @param term the search term * @return the messages that match * @exception SearchException if mail.imap.throwsearchexception is * true and the search is too complex for the IMAP protocol * @exception MessagingException for other failures */ @Override public synchronized Message[] search(SearchTerm term) throws MessagingException { checkOpened(); try { Message[] matchMsgs = null; synchronized(messageCacheLock) { int[] matches = getProtocol().search(term); if (matches != null) matchMsgs = getMessagesBySeqNumbers(matches); } return matchMsgs; } catch (CommandFailedException cfx) { // unsupported charset or search criterion return super.search(term); } catch (SearchException sex) { // too complex for IMAP if (((IMAPStore)store).throwSearchException()) throw sex; return super.search(term); } catch (ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { // bug in our IMAP layer ? throw new MessagingException(pex.getMessage(), pex); } } /** * Search the folder for messages matching the given term. Returns * array of matching messages. Returns an empty array if no matching * messages are found. */ @Override public synchronized Message[] search(SearchTerm term, Message[] msgs) throws MessagingException { checkOpened(); if (msgs.length == 0) // need to return an empty array (not null!) return msgs; try { Message[] matchMsgs = null; synchronized(messageCacheLock) { IMAPProtocol p = getProtocol(); MessageSet[] ms = Utility.toMessageSetSorted(msgs, null); if (ms == null) throw new MessageRemovedException( "Messages have been removed"); int[] matches = p.search(ms, term); if (matches != null) matchMsgs = getMessagesBySeqNumbers(matches); } return matchMsgs; } catch (CommandFailedException cfx) { // unsupported charset or search criterion return super.search(term, msgs); } catch (SearchException sex) { // too complex for IMAP return super.search(term, msgs); } catch (ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { // bug in our IMAP layer ? throw new MessagingException(pex.getMessage(), pex); } } /** * Sort the messages in the folder according to the sort criteria. * The messages are returned in the sorted order, but the order of * the messages in the folder is not changed. <p> * * Depends on the SORT extension - * <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A>. * * @param term the SortTerms * @return the messages in sorted order * @exception MessagingException for failures * @since JavaMail 1.4.4 */ public synchronized Message[] getSortedMessages(SortTerm[] term) throws MessagingException { return getSortedMessages(term, null); } /** * Sort the messages in the folder according to the sort criteria. * The messages are returned in the sorted order, but the order of * the messages in the folder is not changed. Only messages matching * the search criteria are considered. <p> * * Depends on the SORT extension - * <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A>. * * @param term the SortTerms * @param sterm the SearchTerm * @return the messages in sorted order * @exception MessagingException for failures * @since JavaMail 1.4.4 */ public synchronized Message[] getSortedMessages(SortTerm[] term, SearchTerm sterm) throws MessagingException { checkOpened(); try { Message[] matchMsgs = null; synchronized(messageCacheLock) { int[] matches = getProtocol().sort(term, sterm); if (matches != null) matchMsgs = getMessagesBySeqNumbers(matches); } return matchMsgs; } catch (CommandFailedException cfx) { // unsupported charset or search criterion throw new MessagingException(cfx.getMessage(), cfx); } catch (SearchException sex) { // too complex for IMAP throw new MessagingException(sex.getMessage(), sex); } catch (ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { // bug in our IMAP layer ? throw new MessagingException(pex.getMessage(), pex); } } /* * Override Folder method to keep track of whether we have any * message count listeners. Normally we won't have any, so we * can avoid creating message objects to pass to the notify * method. It's too hard to keep track of when all listeners * are removed, and that's a rare case, so we don't try. */ @Override public synchronized void addMessageCountListener(MessageCountListener l) { super.addMessageCountListener(l); hasMessageCountListener = true; } /*********************************************************** * UIDFolder interface methods **********************************************************/ /** * Returns the UIDValidity for this folder. */ @Override public synchronized long getUIDValidity() throws MessagingException { if (opened) // we already have this information return uidvalidity; IMAPProtocol p = null; Status status = null; try { p = getStoreProtocol(); // XXX String[] item = { "UIDVALIDITY" }; status = p.status(fullName, item); } catch (BadCommandException bex) { // Probably a RFC1730 server throw new MessagingException("Cannot obtain UIDValidity", bex); } catch (ConnectionException cex) { // Oops, the store or folder died on us. throwClosedException(cex); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } finally { releaseStoreProtocol(p); } if (status == null) throw new MessagingException("Cannot obtain UIDValidity"); return status.uidvalidity; } /** * Returns the predicted UID that will be assigned to the * next message that is appended to this folder. * If the folder is closed, the STATUS command is used to * retrieve this value. If the folder is open, the value * returned from the SELECT or EXAMINE command is returned. * Note that messages may have been appended to the folder * while it was open and thus this value may be out of * date. <p> * * Servers implementing RFC2060 likely won't return this value * when a folder is opened. Servers implementing RFC3501 * should return this value when a folder is opened. <p> * * @return the UIDNEXT value, or -1 if unknown * @exception MessagingException for failures * @since JavaMail 1.3.3 */ @Override public synchronized long getUIDNext() throws MessagingException { if (opened) // we already have this information return uidnext; IMAPProtocol p = null; Status status = null; try { p = getStoreProtocol(); // XXX String[] item = { "UIDNEXT" }; status = p.status(fullName, item); } catch (BadCommandException bex) { // Probably a RFC1730 server throw new MessagingException("Cannot obtain UIDNext", bex); } catch (ConnectionException cex) { // Oops, the store or folder died on us. throwClosedException(cex); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } finally { releaseStoreProtocol(p); } if (status == null) throw new MessagingException("Cannot obtain UIDNext"); return status.uidnext; } /** * Get the Message corresponding to the given UID. * If no such message exists, <code> null </code> is returned. */ @Override public synchronized Message getMessageByUID(long uid) throws MessagingException { checkOpened(); // insure folder is open IMAPMessage m = null; try { synchronized(messageCacheLock) { Long l = Long.valueOf(uid); if (uidTable != null) { // Check in uidTable m = uidTable.get(l); if (m != null) // found it return m; } else uidTable = new Hashtable<>(); // Check with the server // Issue UID FETCH command getProtocol().fetchSequenceNumber(uid); if (uidTable != null) { // Check in uidTable m = uidTable.get(l); if (m != null) // found it return m; } } } catch(ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } return m; } /** * Get the Messages specified by the given range. <p> * Returns Message objects for all valid messages in this range. * Returns an empty array if no messages are found. */ @Override public synchronized Message[] getMessagesByUID(long start, long end) throws MessagingException { checkOpened(); // insure that folder is open Message[] msgs; // array of messages to be returned try { synchronized(messageCacheLock) { if (uidTable == null) uidTable = new Hashtable<>(); // Issue UID FETCH for given range long[] ua = getProtocol().fetchSequenceNumbers(start, end); List<Message> ma = new ArrayList<>(); // NOTE: Below must be within messageCacheLock region for (int i = 0; i < ua.length; i++) { Message m = uidTable.get(Long.valueOf(ua[i])); if (m != null) // found it ma.add(m); } msgs = ma.toArray(new Message[ma.size()]); } } catch(ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } return msgs; } /** * Get the Messages specified by the given array. <p> * * <code>uids.length()</code> elements are returned. * If any UID in the array is invalid, a <code>null</code> entry * is returned for that element. */ @Override public synchronized Message[] getMessagesByUID(long[] uids) throws MessagingException { checkOpened(); // insure that folder is open try { synchronized(messageCacheLock) { long[] unavailUids = uids; if (uidTable != null) { // to collect unavailable UIDs List<Long> v = new ArrayList<>(); for (long uid : uids) { if (!uidTable.containsKey(uid)) { // This UID has not been loaded yet. v.add(uid); } } int vsize = v.size(); unavailUids = new long[vsize]; for (int i = 0; i < vsize; i++) { unavailUids[i] = v.get(i); } } else uidTable = new Hashtable<>(); if (unavailUids.length > 0) { // Issue UID FETCH request for given uids getProtocol().fetchSequenceNumbers(unavailUids); } // Return array of size = uids.length Message[] msgs = new Message[uids.length]; for (int i = 0; i < uids.length; i++) msgs[i] = (Message)uidTable.get(Long.valueOf(uids[i])); return msgs; } } catch(ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } } /** * Get the UID for the specified message. */ @Override public synchronized long getUID(Message message) throws MessagingException { if (message.getFolder() != this) throw new NoSuchElementException( "Message does not belong to this folder"); checkOpened(); // insure that folder is open if (!(message instanceof IMAPMessage)) throw new MessagingException("message is not an IMAPMessage"); IMAPMessage m = (IMAPMessage)message; // If the message already knows its UID, great .. long uid; if ((uid = m.getUID()) != -1) return uid; synchronized(messageCacheLock) { // Acquire Lock try { IMAPProtocol p = getProtocol(); m.checkExpunged(); // insure that message is not expunged UID u = p.fetchUID(m.getSequenceNumber()); if (u != null) { uid = u.uid; m.setUID(uid); // set message's UID // insert this message into uidTable if (uidTable == null) uidTable = new Hashtable<>(); uidTable.put(Long.valueOf(uid), m); } } catch (ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } } return uid; } /** * Servers that support the UIDPLUS extension * (<A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>) * may indicate that this folder does not support persistent UIDs; * that is, UIDVALIDITY will be different each time the folder is * opened. Only valid when the folder is open. * * @return true if UIDs are not sticky * @exception MessagingException for failures * @exception IllegalStateException if the folder isn't open * @see "RFC 4315" * @since JavaMail 1.6.0 */ public synchronized boolean getUIDNotSticky() throws MessagingException { checkOpened(); return uidNotSticky; } /** * Get or create Message objects for the UIDs. */ private Message[] createMessagesForUIDs(long[] uids) { IMAPMessage[] msgs = new IMAPMessage[uids.length]; for (int i = 0; i < uids.length; i++) { IMAPMessage m = null; if (uidTable != null) m = uidTable.get(Long.valueOf(uids[i])); if (m == null) { // fake it, we don't know what message this really is m = newIMAPMessage(-1); // no sequence number m.setUID(uids[i]); m.setExpunged(true); } msgs[i++] = m; } return msgs; } /** * Returns the HIGHESTMODSEQ for this folder. * * @return the HIGHESTMODSEQ value * @exception MessagingException for failures * @see "RFC 4551" * @since JavaMail 1.5.1 */ public synchronized long getHighestModSeq() throws MessagingException { if (opened) // we already have this information return highestmodseq; IMAPProtocol p = null; Status status = null; try { p = getStoreProtocol(); // XXX if (!p.hasCapability("CONDSTORE")) throw new BadCommandException("CONDSTORE not supported"); String[] item = { "HIGHESTMODSEQ" }; status = p.status(fullName, item); } catch (BadCommandException bex) { // Probably a RFC1730 server throw new MessagingException("Cannot obtain HIGHESTMODSEQ", bex); } catch (ConnectionException cex) { // Oops, the store or folder died on us. throwClosedException(cex); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } finally { releaseStoreProtocol(p); } if (status == null) throw new MessagingException("Cannot obtain HIGHESTMODSEQ"); return status.highestmodseq; } /** * Get the messages that have been changed since the given MODSEQ value. * Also, prefetch the flags for the messages. <p> * * The server must support the CONDSTORE extension. * * @param start the first message number * @param end the last message number * @param modseq the MODSEQ value * @return the changed messages * @exception MessagingException for failures * @see "RFC 4551" * @since JavaMail 1.5.1 */ public synchronized Message[] getMessagesByUIDChangedSince( long start, long end, long modseq) throws MessagingException { checkOpened(); // insure that folder is open try { synchronized (messageCacheLock) { IMAPProtocol p = getProtocol(); if (!p.hasCapability("CONDSTORE")) throw new BadCommandException("CONDSTORE not supported"); // Issue FETCH for given range int[] nums = p.uidfetchChangedSince(start, end, modseq); return getMessagesBySeqNumbers(nums); } } catch(ConnectionException cex) { throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } } /** * Get the quotas for the quotaroot associated with this * folder. Note that many folders may have the same quotaroot. * Quotas are controlled on the basis of a quotaroot, not * (necessarily) a folder. The relationship between folders * and quotaroots depends on the IMAP server. Some servers * might implement a single quotaroot for all folders owned by * a user. Other servers might implement a separate quotaroot * for each folder. A single folder can even have multiple * quotaroots, perhaps controlling quotas for different * resources. * * @return array of Quota objects for the quotaroots associated with * this folder * @exception MessagingException if the server doesn't support the * QUOTA extension */ public Quota[] getQuota() throws MessagingException { return (Quota[])doOptionalCommand("QUOTA not supported", new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { return p.getQuotaRoot(fullName); } }); } /** * Set the quotas for the quotaroot specified in the quota argument. * Typically this will be one of the quotaroots associated with this * folder, as obtained from the <code>getQuota</code> method, but it * need not be. * * @param quota the quota to set * @exception MessagingException if the server doesn't support the * QUOTA extension */ public void setQuota(final Quota quota) throws MessagingException { doOptionalCommand("QUOTA not supported", new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { p.setQuota(quota); return null; } }); } /** * Get the access control list entries for this folder. * * @return array of access control list entries * @exception MessagingException if the server doesn't support the * ACL extension */ public ACL[] getACL() throws MessagingException { return (ACL[])doOptionalCommand("ACL not supported", new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { return p.getACL(fullName); } }); } /** * Add an access control list entry to the access control list * for this folder. * * @param acl the access control list entry to add * @exception MessagingException if the server doesn't support the * ACL extension */ public void addACL(ACL acl) throws MessagingException { setACL(acl, '\0'); } /** * Remove any access control list entry for the given identifier * from the access control list for this folder. * * @param name the identifier for which to remove all ACL entries * @exception MessagingException if the server doesn't support the * ACL extension */ public void removeACL(final String name) throws MessagingException { doOptionalCommand("ACL not supported", new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { p.deleteACL(fullName, name); return null; } }); } /** * Add the rights specified in the ACL to the entry for the * identifier specified in the ACL. If an entry for the identifier * doesn't already exist, add one. * * @param acl the identifer and rights to add * @exception MessagingException if the server doesn't support the * ACL extension */ public void addRights(ACL acl) throws MessagingException { setACL(acl, '+'); } /** * Remove the rights specified in the ACL from the entry for the * identifier specified in the ACL. * * @param acl the identifer and rights to remove * @exception MessagingException if the server doesn't support the * ACL extension */ public void removeRights(ACL acl) throws MessagingException { setACL(acl, '-'); } /** * Get all the rights that may be allowed to the given identifier. * Rights are grouped per RFC 2086 and each group is returned as an * element of the array. The first element of the array is the set * of rights that are always granted to the identifier. Later * elements are rights that may be optionally granted to the * identifier. <p> * * Note that this method lists the rights that it is possible to * assign to the given identifier, <em>not</em> the rights that are * actually granted to the given identifier. For the latter, see * the <code>getACL</code> method. * * @param name the identifier to list rights for * @return array of Rights objects representing possible * rights for the identifier * @exception MessagingException if the server doesn't support the * ACL extension */ public Rights[] listRights(final String name) throws MessagingException { return (Rights[])doOptionalCommand("ACL not supported", new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { return p.listRights(fullName, name); } }); } /** * Get the rights allowed to the currently authenticated user. * * @return the rights granted to the current user * @exception MessagingException if the server doesn't support the * ACL extension */ public Rights myRights() throws MessagingException { return (Rights)doOptionalCommand("ACL not supported", new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { return p.myRights(fullName); } }); } private void setACL(final ACL acl, final char mod) throws MessagingException { doOptionalCommand("ACL not supported", new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { p.setACL(fullName, mod, acl); return null; } }); } /** * Get the attributes that the IMAP server returns with the * LIST response. * * @return array of attributes for this folder * @exception MessagingException for failures * @since JavaMail 1.3.3 */ public synchronized String[] getAttributes() throws MessagingException { checkExists(); if (attributes == null) exists(); // do a LIST to set the attributes return attributes == null ? new String[0] : attributes.clone(); } /** * Use the IMAP IDLE command (see * <A HREF="http://www.ietf.org/rfc/rfc2177.txt">RFC 2177</A>), * if supported by the server, to enter idle mode so that the server * can send unsolicited notifications of new messages arriving, etc. * without the need for the client to constantly poll the server. * Use an appropriate listener to be notified of new messages or * other events. When another thread (e.g., the listener thread) * needs to issue an IMAP comand for this folder, the idle mode will * be terminated and this method will return. Typically the caller * will invoke this method in a loop. <p> * * The mail.imap.minidletime property enforces a minimum delay * before returning from this method, to ensure that other threads * have a chance to issue commands before the caller invokes this * method again. The default delay is 10 milliseconds. * * @exception MessagingException if the server doesn't support the * IDLE extension * @exception IllegalStateException if the folder isn't open * * @since JavaMail 1.4.1 */ public void idle() throws MessagingException { idle(false); } /** * Like {@link #idle}, but if <code>once</code> is true, abort the * IDLE command after the first notification, to allow the caller * to process any notification synchronously. * * @param once only do one notification? * @exception MessagingException if the server doesn't support the * IDLE extension * @exception IllegalStateException if the folder isn't open * * @since JavaMail 1.4.3 */ public void idle(boolean once) throws MessagingException { synchronized (this) { /* * We can't support the idle method if we're using SocketChannels * because SocketChannels don't allow simultaneous read and write. * If we're blocked in a read waiting for IDLE responses, we can't * send the DONE message to abort the IDLE. Sigh. * XXX - We could do select here too, like IdleManager, instead * of blocking in read, but that's more complicated. */ if (protocol != null && protocol.getChannel() != null) throw new MessagingException( "idle method not supported with SocketChannels"); } if (!startIdle(null)) return; /* * We gave up the folder lock so that other threads * can get into the folder far enough to see that we're * in IDLE and abort the IDLE. * * Now we read responses from the IDLE command, especially * including unsolicited notifications from the server. * We don't hold the messageCacheLock while reading because * it protects the idleState and other threads need to be * able to examine the state. * * The messageCacheLock is held in handleIdle while processing * the responses so that we can update the number of messages * in the folder (for example). */ for (;;) { if (!handleIdle(once)) break; } /* * Enforce a minimum delay to give time to threads * processing the responses that came in while we * were idle. */ int minidle = ((IMAPStore)store).getMinIdleTime(); if (minidle > 0) { try { Thread.sleep(minidle); } catch (InterruptedException ex) { // restore the interrupted state, which callers might depend on Thread.currentThread().interrupt(); } } } /** * Start the IDLE command and put this folder into the IDLE state. * IDLE processing is done later in handleIdle(), e.g., called from * the IdleManager. * * @return true if IDLE started, false otherwise * @exception MessagingException if the server doesn't support the * IDLE extension * @exception IllegalStateException if the folder isn't open * @since JavaMail 1.5.2 */ boolean startIdle(final IdleManager im) throws MessagingException { // ASSERT: Must NOT be called with this folder's // synchronization lock held. assert !Thread.holdsLock(this); synchronized(this) { checkOpened(); if (im != null && idleManager != null && im != idleManager) throw new MessagingException( "Folder already being watched by another IdleManager"); Boolean started = (Boolean)doOptionalCommand("IDLE not supported", new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { // if the IdleManager is already watching this folder, // there's nothing to do here if (idleState == IDLE && im != null && im == idleManager) return Boolean.TRUE; // already watching it if (idleState == RUNNING) { p.idleStart(); logger.finest("startIdle: set to IDLE"); idleState = IDLE; idleManager = im; return Boolean.TRUE; } else { // some other thread must be running the IDLE // command, we'll just wait for it to finish // without aborting it ourselves try { // give up lock and wait to be not idle messageCacheLock.wait(); } catch (InterruptedException ex) { // restore the interrupted state, which callers // might depend on Thread.currentThread().interrupt(); } return Boolean.FALSE; } } }); logger.log(Level.FINEST, "startIdle: return {0}", started); return started.booleanValue(); } } /** * Read a response from the server while we're in the IDLE state. * We hold the messageCacheLock while processing the * responses so that we can update the number of messages * in the folder (for example). * * @param once only do one notification? * @return true if we should look for more IDLE responses, * false if IDLE is done * @exception MessagingException for errors * @since JavaMail 1.5.2 */ boolean handleIdle(boolean once) throws MessagingException { Response r = null; do { r = protocol.readIdleResponse(); try { synchronized (messageCacheLock) { if (r.isBYE() && r.isSynthetic() && idleState == IDLE) { /* * If it was a timeout and no bytes were transferred * we ignore it and go back and read again. * If the I/O was otherwise interrupted, and no * bytes were transferred, we take it as a request * to abort the IDLE. */ Exception ex = r.getException(); if (ex instanceof InterruptedIOException && ((InterruptedIOException)ex). bytesTransferred == 0) { if (ex instanceof SocketTimeoutException) { logger.finest( "handleIdle: ignoring socket timeout"); r = null; // repeat do/while loop } else { logger.finest("handleIdle: interrupting IDLE"); IdleManager im = idleManager; if (im != null) { logger.finest( "handleIdle: request IdleManager to abort"); im.requestAbort(this); } else { logger.finest("handleIdle: abort IDLE"); protocol.idleAbort(); idleState = ABORTING; } // normally will exit the do/while loop } continue; } } boolean done = true; try { if (protocol == null || !protocol.processIdleResponse(r)) return false; // done done = false; } finally { if (done) { logger.finest("handleIdle: set to RUNNING"); idleState = RUNNING; idleManager = null; messageCacheLock.notifyAll(); } } if (once) { if (idleState == IDLE) { try { protocol.idleAbort(); } catch (Exception ex) { // ignore any failures, still have to abort. // connection failures will be detected above // in the call to readIdleResponse. } idleState = ABORTING; } } } } catch (ConnectionException cex) { // Oops, the folder died on us. throw new FolderClosedException(this, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } // keep processing responses already in our buffer } while (r == null || protocol.hasResponse()); return true; } /* * If an IDLE command is in progress, abort it if necessary, * and wait until it completes. * ASSERT: Must be called with the message cache lock held. */ void waitIfIdle() throws ProtocolException { assert Thread.holdsLock(messageCacheLock); while (idleState != RUNNING) { if (idleState == IDLE) { IdleManager im = idleManager; if (im != null) { logger.finest("waitIfIdle: request IdleManager to abort"); im.requestAbort(this); } else { logger.finest("waitIfIdle: abort IDLE"); protocol.idleAbort(); idleState = ABORTING; } } else logger.log(Level.FINEST, "waitIfIdle: idleState {0}", idleState); try { // give up lock and wait to be not idle if (logger.isLoggable(Level.FINEST)) logger.finest("waitIfIdle: wait to be not idle: " + Thread.currentThread()); messageCacheLock.wait(); if (logger.isLoggable(Level.FINEST)) logger.finest("waitIfIdle: wait done, idleState " + idleState + ": " + Thread.currentThread()); } catch (InterruptedException ex) { // restore the interrupted state, which callers might depend on Thread.currentThread().interrupt(); // If someone is trying to interrupt us we can't keep going // around the loop waiting for IDLE to complete, but we can't // just return because callers expect the idleState to be // RUNNING when we return. Throwing this exception seems // like the best choice. throw new ProtocolException("Interrupted waitIfIdle", ex); } } } /* * Send the DONE command that aborts the IDLE; used by IdleManager. */ void idleAbort() { synchronized (messageCacheLock) { if (idleState == IDLE && protocol != null) { protocol.idleAbort(); idleState = ABORTING; } } } /* * Send the DONE command that aborts the IDLE and wait for the response; * used by IdleManager. */ void idleAbortWait() { synchronized (messageCacheLock) { if (idleState == IDLE && protocol != null) { protocol.idleAbort(); idleState = ABORTING; // read responses until OK or connection failure try { for (;;) { if (!handleIdle(false)) break; } } catch (Exception ex) { // assume it's a connection failure; nothing more to do logger.log(Level.FINEST, "Exception in idleAbortWait", ex); } logger.finest("IDLE aborted"); } } } /** * Return the SocketChannel for this connection, if any, for use * in IdleManager. */ SocketChannel getChannel() { return protocol != null ? protocol.getChannel() : null; } /** * Send the IMAP ID command (if supported by the server) and return * the result from the server. The ID command identfies the client * to the server and returns information about the server to the client. * See <A HREF="http://www.ietf.org/rfc/rfc2971.txt">RFC 2971</A>. * The returned Map is unmodifiable. * * @param clientParams a Map of keys and values identifying the client * @return a Map of keys and values identifying the server * @exception MessagingException if the server doesn't support the * ID extension * @since JavaMail 1.5.1 */ @SuppressWarnings("unchecked") public Map<String, String> id(final Map<String, String> clientParams) throws MessagingException { checkOpened(); return (Map<String,String>)doOptionalCommand("ID not supported", new ProtocolCommand() { @Override public Object doCommand(IMAPProtocol p) throws ProtocolException { return p.id(clientParams); } }); } /** * Use the IMAP STATUS command to get the indicated item. * The STATUS item may be a standard item such as "RECENT" or "UNSEEN", * or may be a server-specific item. * The folder must be closed. If the item is not found, or the * folder is open, -1 is returned. * * @param item the STATUS item to fetch * @return the value of the STATUS item, or -1 * @exception MessagingException for errors * @since JavaMail 1.5.2 */ public synchronized long getStatusItem(String item) throws MessagingException { if (!opened) { checkExists(); IMAPProtocol p = null; Status status = null; try { p = getStoreProtocol(); // XXX String[] items = { item }; status = p.status(fullName, items); return status != null ? status.getItem(item) : -1; } catch (BadCommandException bex) { // doesn't support STATUS, probably vanilla IMAP4 .. // Could EXAMINE, SEARCH for UNREAD messages and // return the count .. bah, not worth it. return -1; } catch (ConnectionException cex) { throw new StoreClosedException(store, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } finally { releaseStoreProtocol(p); } } return -1; } /** * The response handler. This is the callback routine that is * invoked by the protocol layer. */ /* * ASSERT: This method must be called only when holding the * messageCacheLock. * ASSERT: This method must *not* invoke any other method that * might grab the 'folder' lock or 'message' lock (i.e., any * synchronized methods on IMAPFolder or IMAPMessage) * since that will result in violating the locking hierarchy. */ @Override public void handleResponse(Response r) { assert Thread.holdsLock(messageCacheLock); /* * First, delegate possible ALERT or notification to the Store. */ if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE()) ((IMAPStore)store).handleResponseCode(r); /* * Now check whether this is a BYE or OK response and * handle appropriately. */ if (r.isBYE()) { if (opened) // XXX - accessed without holding folder lock cleanup(false); return; } else if (r.isOK()) { // HIGHESTMODSEQ can be updated on any OK response r.skipSpaces(); if (r.readByte() == '[') { String s = r.readAtom(); if (s.equalsIgnoreCase("HIGHESTMODSEQ")) highestmodseq = r.readLong(); } r.reset(); return; } else if (!r.isUnTagged()) { return; // might be a continuation for IDLE } /* Now check whether this is an IMAP specific response */ if (!(r instanceof IMAPResponse)) { // Probably a bug in our code ! // XXX - should be an assert logger.fine("UNEXPECTED RESPONSE : " + r.toString()); return; } IMAPResponse ir = (IMAPResponse)r; if (ir.keyEquals("EXISTS")) { // EXISTS int exists = ir.getNumber(); if (exists <= realTotal) // Could be the EXISTS following EXPUNGE, ignore 'em return; int count = exists - realTotal; // number of new messages Message[] msgs = new Message[count]; // Add 'count' new IMAPMessage objects into the messageCache messageCache.addMessages(count, realTotal + 1); int oldtotal = total; // used in loop below realTotal += count; total += count; // avoid instantiating Message objects if no listeners. if (hasMessageCountListener) { for (int i = 0; i < count; i++) msgs[i] = messageCache.getMessage(++oldtotal); // Notify listeners. notifyMessageAddedListeners(msgs); } } else if (ir.keyEquals("EXPUNGE")) { // EXPUNGE response. int seqnum = ir.getNumber(); if (seqnum > realTotal) { // A message was expunged that we never knew about. // Exchange will do this. Just ignore the notification. // (Alternatively, we could simulate an EXISTS for the // expunged message before expunging it.) return; } Message[] msgs = null; if (doExpungeNotification && hasMessageCountListener) { // save the Message object first; can't look it // up after it's expunged msgs = new Message[] { getMessageBySeqNumber(seqnum) }; if (msgs[0] == null) // XXX - should never happen msgs = null; } messageCache.expungeMessage(seqnum); // decrement 'realTotal'; but leave 'total' unchanged realTotal--; if (msgs != null) // Do the notification here. notifyMessageRemovedListeners(false, msgs); } else if (ir.keyEquals("VANISHED")) { // after the folder is opened with QRESYNC, a VANISHED response // without the (EARLIER) tag is used instead of the EXPUNGE // response // "VANISHED" SP ["(EARLIER)"] SP known-uids String[] s = ir.readAtomStringList(); if (s == null) { // no (EARLIER) String uids = ir.readAtom(); UIDSet[] uidset = UIDSet.parseUIDSets(uids); // assume no duplicates and no UIDs out of range realTotal -= UIDSet.size(uidset); long[] luid = UIDSet.toArray(uidset); Message[] msgs = createMessagesForUIDs(luid); for (Message m : msgs) { if (m.getMessageNumber() > 0) messageCache.expungeMessage(m.getMessageNumber()); } if (doExpungeNotification && hasMessageCountListener) { notifyMessageRemovedListeners(true, msgs); } } // else if (EARLIER), ignore } else if (ir.keyEquals("FETCH")) { assert ir instanceof FetchResponse : "!ir instanceof FetchResponse"; Message msg = processFetchResponse((FetchResponse)ir); if (msg != null) notifyMessageChangedListeners( MessageChangedEvent.FLAGS_CHANGED, msg); } else if (ir.keyEquals("RECENT")) { // update 'recent' recent = ir.getNumber(); } } /** * Process a FETCH response. * The only unsolicited FETCH response that makes sense * to me (for now) is FLAGS updates, which might include * UID and MODSEQ information. Ignore any other junk. */ private Message processFetchResponse(FetchResponse fr) { IMAPMessage msg = getMessageBySeqNumber(fr.getNumber()); if (msg != null) { // should always be true boolean notify = false; UID uid = fr.getItem(UID.class); if (uid != null && msg.getUID() != uid.uid) { msg.setUID(uid.uid); if (uidTable == null) uidTable = new Hashtable<>(); uidTable.put(Long.valueOf(uid.uid), msg); notify = true; } MODSEQ modseq = fr.getItem(MODSEQ.class); if (modseq != null && msg._getModSeq() != modseq.modseq) { msg.setModSeq(modseq.modseq); /* * XXX - should we update the folder's HIGHESTMODSEQ or not? * if (modseq.modseq > highestmodseq) highestmodseq = modseq.modseq; */ notify = true; } // Get FLAGS response, if present FLAGS flags = fr.getItem(FLAGS.class); if (flags != null) { msg._setFlags(flags); // assume flags changed notify = true; } // handle any extension items that might've changed // XXX - no notifications associated with extension items msg.handleExtensionFetchItems(fr.getExtensionItems()); if (!notify) msg = null; } return msg; } /** * Handle the given array of Responses. * * ASSERT: This method must be called only when holding the * messageCacheLock */ void handleResponses(Response[] r) { for (int i = 0; i < r.length; i++) { if (r[i] != null) handleResponse(r[i]); } } /** * Get this folder's Store's protocol connection. * * When acquiring a store protocol object, it is important to * use the following steps: * * <blockquote><pre> * IMAPProtocol p = null; * try { * p = getStoreProtocol(); * // perform the command * } catch (WhateverException ex) { * // handle it * } finally { * releaseStoreProtocol(p); * } * </pre></blockquote> * * ASSERT: Must be called with this folder's synchronization lock held. * * @return the IMAPProtocol for the Store's connection * @exception ProtocolException for protocol errors */ protected synchronized IMAPProtocol getStoreProtocol() throws ProtocolException { connectionPoolLogger.fine("getStoreProtocol() borrowing a connection"); return ((IMAPStore)store).getFolderStoreProtocol(); } /** * Throw the appropriate 'closed' exception. * * @param cex the ConnectionException * @exception FolderClosedException if the folder is closed * @exception StoreClosedException if the store is closed */ protected synchronized void throwClosedException(ConnectionException cex) throws FolderClosedException, StoreClosedException { // If it's the folder's protocol object, throw a FolderClosedException; // otherwise, throw a StoreClosedException. // If a command has failed because the connection is closed, // the folder will have already been forced closed by the // time we get here and our protocol object will have been // released, so if we no longer have a protocol object we base // this decision on whether we *think* the folder is open. if ((protocol != null && cex.getProtocol() == protocol) || (protocol == null && !reallyClosed)) throw new FolderClosedException(this, cex.getMessage()); else throw new StoreClosedException(store, cex.getMessage()); } /** * Return the IMAPProtocol object for this folder. <p> * * This method will block if necessary to wait for an IDLE * command to finish. * * @return the IMAPProtocol object used when the folder is open * @exception ProtocolException for protocol errors */ protected IMAPProtocol getProtocol() throws ProtocolException { assert Thread.holdsLock(messageCacheLock); waitIfIdle(); // if we no longer have a protocol object after waiting, it probably // means the connection has been closed due to a communnication error, // or possibly because the folder has been closed if (protocol == null) throw new ConnectionException("Connection closed"); return protocol; } /** * A simple interface for user-defined IMAP protocol commands. */ public static interface ProtocolCommand { /** * Execute the user-defined command using the supplied IMAPProtocol * object. * * @param protocol the IMAPProtocol for the connection * @return the results of the command * @exception ProtocolException for protocol errors */ public Object doCommand(IMAPProtocol protocol) throws ProtocolException; } /** * Execute a user-supplied IMAP command. The command is executed * in the appropriate context with the necessary locks held and * using the appropriate <code>IMAPProtocol</code> object. <p> * * This method returns whatever the <code>ProtocolCommand</code> * object's <code>doCommand</code> method returns. If the * <code>doCommand</code> method throws a <code>ConnectionException</code> * it is translated into a <code>StoreClosedException</code> or * <code>FolderClosedException</code> as appropriate. If the * <code>doCommand</code> method throws a <code>ProtocolException</code> * it is translated into a <code>MessagingException</code>. <p> * * The following example shows how to execute the IMAP NOOP command. * Executing more complex IMAP commands requires intimate knowledge * of the <code>com.sun.mail.iap</code> and * <code>com.sun.mail.imap.protocol</code> packages, best acquired by * reading the source code. * * <blockquote><pre> * import com.sun.mail.iap.*; * import com.sun.mail.imap.*; * import com.sun.mail.imap.protocol.*; * * ... * * IMAPFolder f = (IMAPFolder)folder; * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() { * public Object doCommand(IMAPProtocol p) * throws ProtocolException { * p.simpleCommand("NOOP", null); * return null; * } * }); * </pre></blockquote> * <p> * * Here's a more complex example showing how to use the proposed * IMAP SORT extension: * * <blockquote><pre> * import com.sun.mail.iap.*; * import com.sun.mail.imap.*; * import com.sun.mail.imap.protocol.*; * * ... * * IMAPFolder f = (IMAPFolder)folder; * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() { * public Object doCommand(IMAPProtocol p) * throws ProtocolException { * // Issue command * Argument args = new Argument(); * Argument list = new Argument(); * list.writeString("SUBJECT"); * args.writeArgument(list); * args.writeString("UTF-8"); * args.writeString("ALL"); * Response[] r = p.command("SORT", args); * Response response = r[r.length-1]; * * // Grab response * Vector v = new Vector(); * if (response.isOK()) { // command succesful * for (int i = 0, len = r.length; i < len; i++) { * if (!(r[i] instanceof IMAPResponse)) * continue; * * IMAPResponse ir = (IMAPResponse)r[i]; * if (ir.keyEquals("SORT")) { * String num; * while ((num = ir.readAtomString()) != null) * System.out.println(num); * r[i] = null; * } * } * } * * // dispatch remaining untagged responses * p.notifyResponseHandlers(r); * p.handleResult(response); * * return null; * } * }); * </pre></blockquote> * * @param cmd the protocol command * @return the result of the command * @exception MessagingException for failures */ public Object doCommand(ProtocolCommand cmd) throws MessagingException { try { return doProtocolCommand(cmd); } catch (ConnectionException cex) { // Oops, the store or folder died on us. throwClosedException(cex); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } return null; } public Object doOptionalCommand(String err, ProtocolCommand cmd) throws MessagingException { try { return doProtocolCommand(cmd); } catch (BadCommandException bex) { throw new MessagingException(err, bex); } catch (ConnectionException cex) { // Oops, the store or folder died on us. throwClosedException(cex); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } return null; } public Object doCommandIgnoreFailure(ProtocolCommand cmd) throws MessagingException { try { return doProtocolCommand(cmd); } catch (CommandFailedException cfx) { return null; } catch (ConnectionException cex) { // Oops, the store or folder died on us. throwClosedException(cex); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } return null; } protected synchronized Object doProtocolCommand(ProtocolCommand cmd) throws ProtocolException { /* * Check whether we have a protocol object, not whether we're * opened, to allow use of the exsting protocol object in the * open method before the state is changed to "opened". */ if (protocol != null) { synchronized (messageCacheLock) { return cmd.doCommand(getProtocol()); } } // only get here if using store's connection IMAPProtocol p = null; try { p = getStoreProtocol(); return cmd.doCommand(p); } finally { releaseStoreProtocol(p); } } /** * Release the store protocol object. If we borrowed a protocol * object from the connection pool, give it back. If we used our * own protocol object, nothing to do. * * ASSERT: Must be called with this folder's synchronization lock held. * * @param p the IMAPProtocol object */ protected synchronized void releaseStoreProtocol(IMAPProtocol p) { if (p != protocol) ((IMAPStore)store).releaseFolderStoreProtocol(p); else { // XXX - should never happen logger.fine("releasing our protocol as store protocol?"); } } /** * Release the protocol object. * * ASSERT: This method must be called only when holding the * messageCacheLock * * @param returnToPool return the protocol object to the pool? */ protected void releaseProtocol(boolean returnToPool) { if (protocol != null) { protocol.removeResponseHandler(this); if (returnToPool) ((IMAPStore)store).releaseProtocol(this, protocol); else { protocol.disconnect(); // make sure it's disconnected ((IMAPStore)store).releaseProtocol(this, null); } protocol = null; } } /** * Issue a noop command for the connection if the connection has not been * used in more than a second. If <code>keepStoreAlive</code> is true, * also issue a noop over the store's connection. * * ASSERT: This method must be called only when holding the * messageCacheLock * * @param keepStoreAlive keep the Store alive too? * @exception ProtocolException for protocol errors */ protected void keepConnectionAlive(boolean keepStoreAlive) throws ProtocolException { assert Thread.holdsLock(messageCacheLock); if (protocol == null) // in case connection was closed return; if (System.currentTimeMillis() - protocol.getTimestamp() > 1000) { waitIfIdle(); if (protocol != null) protocol.noop(); } if (keepStoreAlive && ((IMAPStore)store).hasSeparateStoreConnection()) { IMAPProtocol p = null; try { p = ((IMAPStore)store).getFolderStoreProtocol(); if (System.currentTimeMillis() - p.getTimestamp() > 1000) p.noop(); } finally { ((IMAPStore)store).releaseFolderStoreProtocol(p); } } } /** * Get the message object for the given sequence number. If * none found, null is returned. * * ASSERT: This method must be called only when holding the * messageCacheLock * * @param seqnum the message sequence number * @return the IMAPMessage object */ protected IMAPMessage getMessageBySeqNumber(int seqnum) { if (seqnum > messageCache.size()) { // Microsoft Exchange will sometimes return message // numbers that it has not yet notified the client // about via EXISTS; ignore those messages here. // GoDaddy IMAP does this too. if (logger.isLoggable(Level.FINE)) logger.fine("ignoring message number " + seqnum + " outside range " + messageCache.size()); return null; } return messageCache.getMessageBySeqnum(seqnum); } /** * Get the message objects for the given sequence numbers. * * ASSERT: This method must be called only when holding the * messageCacheLock * * @param seqnums the array of message sequence numbers * @return the IMAPMessage objects * @since JavaMail 1.5.3 */ protected IMAPMessage[] getMessagesBySeqNumbers(int[] seqnums) { IMAPMessage[] msgs = new IMAPMessage[seqnums.length]; int nulls = 0; // Map seq-numbers into actual Messages. for (int i = 0; i < seqnums.length; i++) { msgs[i] = getMessageBySeqNumber(seqnums[i]); if (msgs[i] == null) nulls++; } if (nulls > 0) { // compress the array to remove the nulls IMAPMessage[] nmsgs = new IMAPMessage[seqnums.length - nulls]; for (int i = 0, j = 0; i < msgs.length; i++) { if (msgs[i] != null) nmsgs[j++] = msgs[i]; } msgs = nmsgs; } return msgs; } private boolean isDirectory() { return ((type & HOLDS_FOLDERS) != 0); } } /** * An object that holds a Message object * and reports its size and writes it to another OutputStream * on demand. Used by appendMessages to avoid the need to * buffer the entire message in memory in a single byte array * before sending it to the server. */ class MessageLiteral implements Literal { private Message msg; private int msgSize = -1; private byte[] buf; // the buffered message, if not null public MessageLiteral(Message msg, int maxsize) throws MessagingException, IOException { this.msg = msg; // compute the size here so exceptions can be returned immediately LengthCounter lc = new LengthCounter(maxsize); OutputStream os = new CRLFOutputStream(lc); msg.writeTo(os); os.flush(); msgSize = lc.getSize(); buf = lc.getBytes(); } @Override public int size() { return msgSize; } @Override public void writeTo(OutputStream os) throws IOException { // the message should not change between the constructor and this call try { if (buf != null) os.write(buf, 0, msgSize); else { os = new CRLFOutputStream(os); msg.writeTo(os); } } catch (MessagingException mex) { // exceptions here are bad, "should" never happen throw new IOException("MessagingException while appending message: " + mex); } } } /** * Count the number of bytes written to the stream. * Also, save a copy of small messages to avoid having to process * the data again. */ class LengthCounter extends OutputStream { private int size = 0; private byte[] buf; private int maxsize; public LengthCounter(int maxsize) { buf = new byte[8192]; this.maxsize = maxsize; } @Override public void write(int b) { int newsize = size + 1; if (buf != null) { if (newsize > maxsize && maxsize >= 0) { buf = null; } else if (newsize > buf.length) { byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)]; System.arraycopy(buf, 0, newbuf, 0, size); buf = newbuf; buf[size] = (byte)b; } else { buf[size] = (byte)b; } } size = newsize; } @Override public void write(byte b[], int off, int len) { if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return; } int newsize = size + len; if (buf != null) { if (newsize > maxsize && maxsize >= 0) { buf = null; } else if (newsize > buf.length) { byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)]; System.arraycopy(buf, 0, newbuf, 0, size); buf = newbuf; System.arraycopy(b, off, buf, size, len); } else { System.arraycopy(b, off, buf, size, len); } } size = newsize; } @Override public void write(byte[] b) throws IOException { write(b, 0, b.length); } public int getSize() { return size; } public byte[] getBytes() { return buf; } }
⏎ com/sun/mail/imap/IMAPFolder.java
Or download all of them as a single archive file:
File name: javax.mail-1.6.2-sources.jar File size: 851487 bytes Release date: 2018-08-29 Download
⇒ Download and Install javax.mail-1.5.4.jar
⇐ Download and Install javax.mail-1.6.2.jar
2016-01-07, 9865👍, 0💬
Popular Posts:
Apache Commons Lang 3 is the 3rd version of Apache Commons Lang, which provides a host of helper uti...
How to run "jar" command from JDK tools.jar file? "jar" is the JAR (Java Archive) file management co...
The Jakarta-ORO Java classes are a set of text-processing Java classes that provide Perl5 compatible...
JDK 1.1 source code directory contains Java source code for JDK 1.1 core classes: "C:\fyicenter\jdk-...
What is the sax\Writer.java provided in the Apache Xerces package? I have Apache Xerces 2.11.0 insta...