GERONIMO-4584 Opening an IMAP folder with *logs* of emails is very expensive in terms of memory and cpu
git-svn-id: https://svn.apache.org/repos/asf/geronimo/javamail/trunk@761642 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java
index 7c143d6..ab65156 100644
--- a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/store/imap/IMAPFolder.java
@@ -58,8 +58,8 @@
/**
* Special profile item used for fetching SIZE and HEADER information.
- * These items are extensions that Sun has added to their IMAPFolder immplementation.
- * We're supporting the same set.
+ * These items are extensions that Sun has added to their IMAPFolder immplementation.
+ * We're supporting the same set.
*/
public static class FetchProfileItem extends FetchProfile.Item {
public static final FetchProfileItem HEADERS = new FetchProfileItem("HEADERS");
@@ -69,17 +69,17 @@
super(name);
}
}
-
- // marker that we don't know the separator yet for this folder.
- // This occurs when we obtain a folder reference from the
- // default folder. At that point, we've not queried the
- // server for specifics yet.
- static final protected char UNDETERMINED = 0;
-
+
+ // marker that we don't know the separator yet for this folder.
+ // This occurs when we obtain a folder reference from the
+ // default folder. At that point, we've not queried the
+ // server for specifics yet.
+ static final protected char UNDETERMINED = 0;
+
// our attached session
protected Session session;
// retrieved messages, mapped by sequence number.
- protected LinkedList messages;
+ protected Map messageCache;
// mappings of UIDs to retrieved messages.
protected Map uidCache;
@@ -97,10 +97,10 @@
// the subscription status
protected boolean subscribed = false;
- // the message identifier ticker, used to assign message numbers.
- protected int nextMessageID = 1;
- // the current count of messages in our cache.
- protected int maxSequenceNumber = 0;
+ // the message identifier ticker, used to assign message numbers.
+ protected int nextMessageID = 1;
+ // the current count of messages in our cache.
+ protected int maxSequenceNumber = 0;
// the reported count of new messages (updated as a result of untagged message resposes)
protected int recentMessages = -1;
// the reported count of unseen messages
@@ -115,26 +115,26 @@
protected Flags availableFlags;
// Our cached status information. We will only hold this for the timeout interval.
protected IMAPMailboxStatus cachedStatus;
- // Folder information retrieved from the server. Good info here indicates the
- // folder exists.
- protected IMAPListResponse listInfo;
+ // Folder information retrieved from the server. Good info here indicates the
+ // folder exists.
+ protected IMAPListResponse listInfo;
// the configured status cache timeout value.
protected long statusCacheTimeout;
// the last time we took a status snap shot.
protected long lastStatusTimeStamp;
- // Our current connection. We get one of these when opened, and release it when closed.
- // We do this because for any folder (and message) operations, the folder must be selected on
- // the connection.
- // Note, however, that there are operations which will require us to borrow a connection
- // temporarily because we need to touch the server when the folder is not open. In those
- // cases, we grab a connection, then immediately return it to the pool.
- protected IMAPConnection currentConnection;
-
-
+ // Our current connection. We get one of these when opened, and release it when closed.
+ // We do this because for any folder (and message) operations, the folder must be selected on
+ // the connection.
+ // Note, however, that there are operations which will require us to borrow a connection
+ // temporarily because we need to touch the server when the folder is not open. In those
+ // cases, we grab a connection, then immediately return it to the pool.
+ protected IMAPConnection currentConnection;
+
+
/**
* Super class constructor the base IMAPFolder class.
- *
+ *
* @param store The javamail store this folder is attached to.
* @param fullname The fully qualified name of this folder.
* @param separator The separtor character used to delimit the different
@@ -147,29 +147,29 @@
this.session = store.getSession();
this.fullname = fullname;
this.separator = separator;
- // get the status timeout value from the folder.
- statusCacheTimeout = store.statusCacheTimeout;
+ // get the status timeout value from the folder.
+ statusCacheTimeout = store.statusCacheTimeout;
}
/**
* Retrieve the folder name. This is the simple folder
* name at the its hiearchy level. This can be invoked when the folder is closed.
- *
+ *
* @return The folder's name.
*/
public String getName() {
- // At the time we create the folder, we might not know the separator character yet.
- // Because of this we need to delay creating the name element until
- // it's required.
+ // At the time we create the folder, we might not know the separator character yet.
+ // Because of this we need to delay creating the name element until
+ // it's required.
if (name == null) {
// extract the name from the full name
- int lastLevel = -1;
+ int lastLevel = -1;
try {
lastLevel = fullname.lastIndexOf(getSeparator());
} catch (MessagingException e) {
- // not likely to occur, but the link could go down before we
- // get this. Just assume a failure to locate the character
- // occurred.
+ // not likely to occur, but the link could go down before we
+ // get this. Just assume a failure to locate the character
+ // occurred.
}
if (lastLevel == -1) {
name = fullname;
@@ -202,11 +202,11 @@
* @throws MessagingException
*/
public Folder getParent() throws MessagingException {
- // NB: We need to use the method form because the separator
- // might not have been retrieved from the server yet.
- char separator = getSeparator();
- // we don't hold a reference to the parent folder, as that would pin the instance in memory
- // as long as any any leaf item in the hierarchy is still open.
+ // NB: We need to use the method form because the separator
+ // might not have been retrieved from the server yet.
+ char separator = getSeparator();
+ // we don't hold a reference to the parent folder, as that would pin the instance in memory
+ // as long as any any leaf item in the hierarchy is still open.
int lastLevel = fullname.lastIndexOf(separator);
// no parent folder? Get the root one from the Store.
if (lastLevel == -1) {
@@ -227,33 +227,33 @@
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized boolean exists() throws MessagingException {
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
- return checkExistance(connection);
+ return checkExistance(connection);
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
-
+
/**
- * Internal routine for checking existance using an
+ * Internal routine for checking existance using an
* already obtained connection. Used for situations
- * where the list information needs updating but
- * we'd end up acquiring a new connection because
- * the folder isn't open yet.
- *
+ * where the list information needs updating but
+ * we'd end up acquiring a new connection because
+ * the folder isn't open yet.
+ *
* @param connection The connection to use.
- *
+ *
* @return true if the folder exists, false for non-existence.
* @exception MessagingException
*/
private boolean checkExistance(IMAPConnection connection) throws MessagingException {
// get the list response for this folder.
List responses = connection.list("", fullname);
- // NB, this grabs the latest information and updates
- // the type information also. Note also that we need to
- // use the mailbox name, not the full name. This is so
- // the namespace folders will return the correct response.
+ // NB, this grabs the latest information and updates
+ // the type information also. Note also that we need to
+ // use the mailbox name, not the full name. This is so
+ // the namespace folders will return the correct response.
listInfo = findListResponse(responses, getMailBoxName());
if (listInfo == null) {
@@ -269,10 +269,10 @@
folderType |= HOLDS_MESSAGES;
}
- // also update the separator information. This will allow
- // use to skip a call later
+ // also update the separator information. This will allow
+ // use to skip a call later
separator = listInfo.separator;
- // this can be omitted in the response, so assume a default
+ // this can be omitted in the response, so assume a default
if (separator == '\0') {
separator = '/';
}
@@ -280,7 +280,7 @@
// updated ok, so it must be there.
return true;
}
-
+
/**
@@ -290,9 +290,9 @@
* <li>'*' which matches any character including hierarchy delimiters</li>
* </ul>
* This can be invoked when the folder is closed.
- *
+ *
* @param pattern the pattern to search for
- *
+ *
* @return a possibly empty array containing Folders that matched the pattern
* @throws MessagingException
* if there was a problem accessing the store
@@ -309,9 +309,9 @@
* If the store does not support the concept of subscription then this should match against
* all folders; the default implementation of this method achieves this by defaulting to the
* {@link #list(String)} method.
- *
+ *
* @param pattern the pattern to search for
- *
+ *
* @return a possibly empty array containing subscribed Folders that matched the pattern
* @throws MessagingException
* if there was a problem accessing the store
@@ -330,26 +330,26 @@
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized char getSeparator() throws MessagingException {
- // not determined yet, we need to ask the server for the information
+ // not determined yet, we need to ask the server for the information
if (separator == UNDETERMINED) {
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
List responses = connection.list("", fullname);
IMAPListResponse info = findListResponse(responses, fullname);
- // if we didn't get any hits, then we just assume a reasonable default.
+ // if we didn't get any hits, then we just assume a reasonable default.
if (info == null) {
separator = '/';
}
else {
separator = info.separator;
- // this can be omitted in the response, so assume a default
+ // this can be omitted in the response, so assume a default
if (separator == '\0') {
separator = '/';
}
}
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
return separator;
@@ -358,19 +358,19 @@
/**
* Return whether this folder can hold just messages or also
- * subfolders.
+ * subfolders.
*
- * @return The combination of Folder.HOLDS_MESSAGES and Folder.HOLDS_FOLDERS, depending
- * on the folder capabilities.
+ * @return The combination of Folder.HOLDS_MESSAGES and Folder.HOLDS_FOLDERS, depending
+ * on the folder capabilities.
* @exception MessagingException
*/
public int getType() throws MessagingException {
- // checking the validity will update the type information
- // if it succeeds.
+ // checking the validity will update the type information
+ // if it succeeds.
checkFolderValidity();
return folderType;
}
-
+
/**
* Create a new folder capable of containing subfolder and/or messages as
@@ -378,18 +378,18 @@
* name will be recursively created.
* If the folder was sucessfully created, a {@link FolderEvent#CREATED CREATED FolderEvent}
* is sent to all FolderListeners registered with this Folder or with the Store.
- *
+ *
* @param newType the type, indicating if this folder should contain subfolders, messages or both
- *
+ *
* @return true if the folder was sucessfully created
* @throws MessagingException
* if there was a problem accessing the store
*/
public synchronized boolean create(int newType) throws MessagingException {
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
- // by default, just create using the fullname.
+ // by default, just create using the fullname.
String newPath = fullname;
// if this folder is expected to only hold additional folders, we need to
@@ -400,21 +400,21 @@
try {
// go create this
connection.createMailbox(newPath);
- // verify this exists...also updates some of the status
- boolean reallyCreated = checkExistance(connection);
+ // verify this exists...also updates some of the status
+ boolean reallyCreated = checkExistance(connection);
// broadcast a creation event.
notifyFolderListeners(FolderEvent.CREATED);
- return reallyCreated;
+ return reallyCreated;
} catch (MessagingException e) {
//TODO add folder level debug logging.
}
// we have a failure
return false;
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
-
+
/**
* Return the subscription status of this folder.
@@ -424,7 +424,7 @@
*/
public synchronized boolean isSubscribed() {
try {
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
// get the lsub response for this folder.
List responses = connection.listSubscribed("", fullname);
@@ -434,18 +434,18 @@
return false;
}
else {
- // a NOSELECT flag response indicates the mailbox is no longer
- // selectable, so it's also no longer subscribed to.
- return !response.noselect;
+ // a NOSELECT flag response indicates the mailbox is no longer
+ // selectable, so it's also no longer subscribed to.
+ return !response.noselect;
}
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
} catch (MessagingException e) {
- // Can't override to throw a MessagingException on this method, so
- // just swallow any exceptions and assume false is the answer.
+ // Can't override to throw a MessagingException on this method, so
+ // just swallow any exceptions and assume false is the answer.
}
- return false;
+ return false;
}
@@ -456,8 +456,8 @@
* The new subscription state.
*/
public synchronized void setSubscribed(boolean flag) throws MessagingException {
- IMAPConnection connection = getConnection();
- try {
+ IMAPConnection connection = getConnection();
+ try {
if (flag) {
connection.subscribe(fullname);
}
@@ -465,7 +465,7 @@
connection.unsubscribe(fullname);
}
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
@@ -480,7 +480,7 @@
public synchronized boolean hasNewMessages() throws MessagingException {
// the folder must exist for this to work.
checkFolderValidity();
-
+
// get the freshest status information.
refreshStatus(true);
// return the indicator from the message state.
@@ -498,17 +498,17 @@
*/
public Folder getFolder(String name) throws MessagingException {
// this must be a real, valid folder to hold a subfolder
- checkFolderValidity();
+ checkFolderValidity();
if (!holdsFolders()) {
- throw new MessagingException("Folder " + fullname + " cannot hold subfolders");
+ throw new MessagingException("Folder " + fullname + " cannot hold subfolders");
}
- // our separator does not get determined until we ping the server for it. We
- // might need to do that now, so we need to use the getSeparator() method to retrieve this.
- char separator = getSeparator();
-
+ // our separator does not get determined until we ping the server for it. We
+ // might need to do that now, so we need to use the getSeparator() method to retrieve this.
+ char separator = getSeparator();
+
return new IMAPFolder((IMAPStore)store, fullname + separator + name, separator);
}
-
+
/**
* Delete this folder and possibly any subfolders. This operation can only be
@@ -561,7 +561,7 @@
}
}
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
// delete this one now.
connection.deleteMailbox(fullname);
@@ -575,7 +575,7 @@
} catch (MessagingException e) {
// ignored
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
return false;
}
@@ -596,12 +596,12 @@
// but we must also exist
checkFolderValidity();
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
// delete this one now.
connection.renameMailbox(fullname, f.getFullName());
- // we renamed, so get a fresh set of status
- refreshStatus(false);
+ // we renamed, so get a fresh set of status
+ refreshStatus(false);
// notify interested parties about the deletion.
notifyFolderRenamedListeners(f);
@@ -609,7 +609,7 @@
} catch (MessagingException e) {
// ignored
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
return false;
}
@@ -635,12 +635,12 @@
// can only be performed on a closed folder
checkClosed();
// ask the store to kindly hook us up with a connection.
- // We're going to hang on to this until we're closed, so store it in
- // the Folder field. We need to make sure our mailbox is selected while
- // we're working things.
- currentConnection = ((IMAPStore)store).getFolderConnection(this);
- // we need to make ourselves a handler of unsolicited responses
- currentConnection.addResponseHandler(this);
+ // We're going to hang on to this until we're closed, so store it in
+ // the Folder field. We need to make sure our mailbox is selected while
+ // we're working things.
+ currentConnection = ((IMAPStore)store).getFolderConnection(this);
+ // we need to make ourselves a handler of unsolicited responses
+ currentConnection.addResponseHandler(this);
// record our open mode
this.mode = mode;
@@ -656,9 +656,9 @@
throw new ReadOnlyFolderException(this, "Cannot open READ_ONLY folder in READ_WRITE mode");
}
}
-
- // save this status and when we got it for later updating.
- cachedStatus = status;
+
+ // save this status and when we got it for later updating.
+ cachedStatus = status;
// mark when we got this
lastStatusTimeStamp = System.currentTimeMillis();
@@ -672,49 +672,26 @@
availableFlags = status.availableFlags;
permanentFlags = status.permanentFlags;
- // create a our caches
- messages = new LinkedList();
+ // create a our caches. These are empty initially
+ messageCache = new HashMap();
uidCache = new HashMap();
- // this is a real pain, but because we need to track updates
- // to message sequence numbers while the folder is open, the
- // messages list needs to be populated with Message objects
- // to keep track of things. The IMAPMessage objects will not
- // retrieve information from the server until required, so they're
- // relatively lightweight at this point.
- populateMessageCache();
// we're open for business folks!
folderOpen = true;
notifyConnectionListeners(ConnectionEvent.OPENED);
} finally {
- // NB: this doesn't really release this, but it does drive
- // the processing of any unsolicited responses.
- releaseConnection(currentConnection);
+ // NB: this doesn't really release this, but it does drive
+ // the processing of any unsolicited responses.
+ releaseConnection(currentConnection);
}
}
}
-
-
- /**
- * Populate the message cache with dummy messages, ensuring we're filled
- * up to the server-reported number of messages.
- *
- * @exception MessagingException
- */
- protected void populateMessageCache() {
- // spin through the server-reported number of messages and add a dummy one to
- // the cache. The message number is assigned from the id counter, the
- // sequence number is the cache position + 1.
- for (int i = messages.size(); i < maxSequenceNumber; i++) {
- messages.add(new IMAPMessage(this, ((IMAPStore)store), nextMessageID++, i+1));
- }
- }
-
+
/**
* Close this folder; it must already be open.
* A @link ConnectionEvent#CLOSED} event is sent to all listeners registered
- {*
+ {*
* with this folder.
*
* @param expunge whether to expunge all deleted messages
@@ -723,56 +700,56 @@
public synchronized void close(boolean expunge) throws MessagingException {
// Can only be performed on an open folder
checkOpen();
- cleanupFolder(expunge, false);
+ cleanupFolder(expunge, false);
}
-
-
+
+
/**
* Do folder cleanup. This is used both for normal
* close operations, and adnormal closes where the
* server has sent us a BYE message.
- *
+ *
* @param expunge Indicates whether open messages should be expunged.
* @param disconnected
* The disconnected flag. If true, the server has cut
* us off, which means our connection can not be returned
* to the connection pool.
- *
+ *
* @exception MessagingException
*/
protected void cleanupFolder(boolean expunge, boolean disconnected) throws MessagingException {
folderOpen = false;
- uidCache = null;
- messages = null;
+ uidCache = null;
+ messageCache = null;
// if we have a connection active at the moment
if (currentConnection != null) {
// was this a forced disconnect by the server?
if (disconnected) {
- currentConnection.setClosed();
+ currentConnection.setClosed();
}
else {
- // The CLOSE operation depends on what mode was used to select the mailbox.
- // If we're open in READ-WRITE mode, we used a SELECT operation. When CLOSE
- // is issued, any deleted messages will be expunged. If we've been asked not
- // to expunge the messages, we have a problem. The solution is to reselect the
- // mailbox using EXAMINE, which will not expunge messages when closed.
+ // The CLOSE operation depends on what mode was used to select the mailbox.
+ // If we're open in READ-WRITE mode, we used a SELECT operation. When CLOSE
+ // is issued, any deleted messages will be expunged. If we've been asked not
+ // to expunge the messages, we have a problem. The solution is to reselect the
+ // mailbox using EXAMINE, which will not expunge messages when closed.
if (mode == READ_WRITE && !expunge) {
- // we can ignore the result...we're just switching modes.
+ // we can ignore the result...we're just switching modes.
currentConnection.openMailbox(fullname, true);
}
-
- // have this close the selected mailbox
- currentConnection.closeMailbox();
+
+ // have this close the selected mailbox
+ currentConnection.closeMailbox();
}
- currentConnection.removeResponseHandler(this);
- // we need to release the connection to the Store once we're closed
- ((IMAPStore)store).releaseFolderConnection(this, currentConnection);
- currentConnection = null;
+ currentConnection.removeResponseHandler(this);
+ // we need to release the connection to the Store once we're closed
+ ((IMAPStore)store).releaseFolderConnection(this, currentConnection);
+ currentConnection = null;
}
notifyConnectionListeners(ConnectionEvent.CLOSED);
}
-
+
/**
* Tests the open status of the folder.
*
@@ -793,8 +770,8 @@
return new Flags(permanentFlags);
}
else {
- // a null return is expected if not there.
- return null;
+ // a null return is expected if not there.
+ return null;
}
}
@@ -823,21 +800,21 @@
* The default implmentation of this method iterates over all messages
* in the folder; subclasses should override if possible to provide a more
* efficient implementation.
- *
- * NB: This is an override of the default Folder implementation, which
- * examines each of the messages in the folder. IMAP has more efficient
- * mechanisms for grabbing the information.
+ *
+ * NB: This is an override of the default Folder implementation, which
+ * examines each of the messages in the folder. IMAP has more efficient
+ * mechanisms for grabbing the information.
*
* @return the number of new messages, or -1 if unknown
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized int getNewMessageCount() throws MessagingException {
- // the folder must be a real one for this to work.
- checkFolderValidity();
- // now get current status from the folder
- refreshStatus(false);
- // this should be current now.
- return recentMessages;
+ // the folder must be a real one for this to work.
+ checkFolderValidity();
+ // now get current status from the folder
+ refreshStatus(false);
+ // this should be current now.
+ return recentMessages;
}
@@ -849,10 +826,10 @@
* The default implmentation of this method iterates over all messages
* in the folder; subclasses should override if possible to provide a more
* efficient implementation.
- *
- * NB: This is an override of the default Folder implementation, which
- * examines each of the messages in the folder. IMAP has more efficient
- * mechanisms for grabbing the information.
+ *
+ * NB: This is an override of the default Folder implementation, which
+ * examines each of the messages in the folder. IMAP has more efficient
+ * mechanisms for grabbing the information.
*
* @return the number of new messages, or -1 if unknown
* @throws MessagingException if there was a problem accessing the store
@@ -870,9 +847,9 @@
// UNSEEN is a false test on SEEN using the search criteria.
SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.SEEN), false);
-
+
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
// search using the connection directly rather than calling our search() method so we don't
// need to instantiate each of the matched messages. We're really only interested in the count
@@ -881,7 +858,7 @@
// update the unseen count.
unseenMessages = matches == null ? 0 : matches.length;
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
// return our current message count.
@@ -917,9 +894,9 @@
// UNSEEN is a false test on SEEN using the search criteria.
SearchTerm criteria = new FlagTerm(new Flags(Flags.Flag.DELETED), true);
-
+
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
// search using the connection directly rather than calling our search() method so we don't
// need to instantiate each of the matched messages. We're really only interested in the count
@@ -927,7 +904,7 @@
int[] matches = connection.searchMailbox(criteria);
return matches == null ? 0 : matches.length;
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
}
@@ -939,9 +916,9 @@
* Clients should note that the index for a specific message may change
* if the folder is expunged; {@link Message} objects should be used as
* references instead.
- *
+ *
* @param msgNum The message sequence number of the target message.
- *
+ *
* @return the message
* @throws MessagingException
* if there was a problem accessing the store
@@ -952,19 +929,28 @@
// Check the validity of the message number. This may require pinging the server to
// see if there are new messages in the folder.
checkMessageValidity(msgNum);
- // ok, if the message number is within range, we should have this in the
- // messages list. Just return the element.
- return (Message)messages.get(msgNum - 1);
+ // create the mapping key for this
+ Integer messageKey = new Integer(msgNum);
+ // ok, if the message number is within range, we should have this in the
+ // messages list. Just return the element.
+ Message message = (Message)messageCache.get(messageKey);
+ // if not in the cache, create a dummy add it in. The message body will be
+ // retrieved on demand
+ if (message == null) {
+ message = new IMAPMessage(this, ((IMAPStore)store), nextMessageID++, msgNum);
+ messageCache.put(messageKey, message);
+ }
+ return message;
}
/**
* Retrieve a range of messages for this folder.
* messages indices start at 1 not zero.
- *
+ *
* @param start Index of the first message to fetch, inclusive.
* @param end Index of the last message to fetch, inclusive.
- *
+ *
* @return An array of the fetched messages.
* @throws MessagingException
* if there was a problem accessing the store
@@ -973,13 +959,13 @@
// Can only be performed on an Open folder
checkOpen();
Message[] messageRange = new Message[end - start + 1];
-
+
for (int i = 0; i < messageRange.length; i++) {
- // NB: getMessage() requires values that are origin 1, so there's
- // no need to adjust the value by other than the start position.
+ // NB: getMessage() requires values that are origin 1, so there's
+ // no need to adjust the value by other than the start position.
messageRange[i] = getMessage(start + i);
}
- return messageRange;
+ return messageRange;
}
@@ -988,18 +974,18 @@
* to all listeners registered with this folder when all messages have been appended.
* If the array contains a previously expunged message, it must be re-appended to the Store
* and implementations must not abort this operation.
- *
+ *
* @param msgs The array of messages to append to the folder.
- *
+ *
* @throws MessagingException
* if there was a problem accessing the store
*/
public synchronized void appendMessages(Message[] msgs) throws MessagingException {
- checkFolderValidity();
+ checkFolderValidity();
for (int i = 0; i < msgs.length; i++) {
- Message msg = msgs[i];
-
- appendMessage(msg);
+ Message msg = msgs[i];
+
+ appendMessage(msg);
}
}
@@ -1014,52 +1000,52 @@
* @see FetchProfile
*/
public void fetch(Message[] messages, FetchProfile profile) throws MessagingException {
-
- // we might already have the information being requested, so ask each of the
- // messages in the list to evaluate itself against the profile. We'll only ask
- // the server to send information that's required.
- List fetchSet = new ArrayList();
-
+
+ // we might already have the information being requested, so ask each of the
+ // messages in the list to evaluate itself against the profile. We'll only ask
+ // the server to send information that's required.
+ List fetchSet = new ArrayList();
+
for (int i = 0; i < messages.length; i++) {
- Message msg = messages[i];
- // the message is missing some of the information still. Keep this in the list.
- // even if the message is only missing one piece of information, we still fetch everything.
+ Message msg = messages[i];
+ // the message is missing some of the information still. Keep this in the list.
+ // even if the message is only missing one piece of information, we still fetch everything.
if (((IMAPMessage)msg).evaluateFetch(profile)) {
- fetchSet.add(msg);
+ fetchSet.add(msg);
}
}
-
- // we've got everything already, no sense bothering the server
+
+ // we've got everything already, no sense bothering the server
if (fetchSet.isEmpty()) {
return;
}
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
- // ok, from this point onward, we don't want any threads messing with the
- // message cache. A single processed EXPUNGE could make for a very bad day
+ // ok, from this point onward, we don't want any threads messing with the
+ // message cache. A single processed EXPUNGE could make for a very bad day
synchronized(this) {
- // get the message set for this
- String messageSet = generateMessageSet(fetchSet);
- // fetch all of the responses
- List responses = connection.fetch(messageSet, profile);
-
- // IMPORTANT: We must do our updates while synchronized to keep the
- // cache from getting updated underneath us. This includes
- // not releasing the connection until we're done to delay processing any
- // pending expunge responses.
+ // get the message set for this
+ String messageSet = generateMessageSet(fetchSet);
+ // fetch all of the responses
+ List responses = connection.fetch(messageSet, profile);
+
+ // IMPORTANT: We must do our updates while synchronized to keep the
+ // cache from getting updated underneath us. This includes
+ // not releasing the connection until we're done to delay processing any
+ // pending expunge responses.
for (int i = 0; i < responses.size(); i++) {
- IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
- Message msg = getMessage(response.getSequenceNumber());
- // Belt and Braces. This should never be false.
+ IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
+ Message msg = getMessage(response.getSequenceNumber());
+ // Belt and Braces. This should never be false.
if (msg != null) {
- // have the message apply this to itself.
- ((IMAPMessage)msg).updateMessageInformation(response);
+ // have the message apply this to itself.
+ ((IMAPMessage)msg).updateMessageInformation(response);
}
}
}
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
return;
}
@@ -1069,18 +1055,18 @@
* This method may be overridden by subclasses that can optimize the setting
* of flags on multiple messages at once; the default implementation simply calls
* {@link Message#setFlags(Flags, boolean)} for each supplied messages.
- *
+ *
* @param messages whose flags should be set
* @param flags the set of flags to modify
* @param set Indicates whether the flags should be set or cleared.
- *
+ *
* @throws MessagingException
* if there was a problem accessing the store
*/
public void setFlags(Message[] messages, Flags flags, boolean set) throws MessagingException {
- // this is a list of messages for the change broadcast after the update
- List updatedMessages = new ArrayList();
-
+ // this is a list of messages for the change broadcast after the update
+ List updatedMessages = new ArrayList();
+
synchronized(this) {
// the folder must be open and writeable.
checkOpenReadWrite();
@@ -1098,61 +1084,61 @@
return;
}
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
// and have the connection set this
List responses = connection.setFlags(messageSet, flags, set);
// retrieve each of the messages from our cache, and do the flag update.
- // we need to keep the list so we can broadcast a change update event
- // when we're finished.
+ // we need to keep the list so we can broadcast a change update event
+ // when we're finished.
for (int i = 0; i < responses.size(); i++) {
- IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
+ IMAPFetchResponse response = (IMAPFetchResponse)responses.get(i);
- // get the updated message and update the internal state.
- Message message = getMessage(response.sequenceNumber);
- // this shouldn't happen, but it might have been expunged too.
+ // get the updated message and update the internal state.
+ Message message = getMessage(response.sequenceNumber);
+ // this shouldn't happen, but it might have been expunged too.
if (message != null) {
- ((IMAPMessage)message).updateMessageInformation(response);
- updatedMessages.add(message);
- }
+ ((IMAPMessage)message).updateMessageInformation(response);
+ updatedMessages.add(message);
+ }
}
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
-
- // ok, we're no longer holding the lock. Now go broadcast the update for each
- // of the affected messages.
+
+ // ok, we're no longer holding the lock. Now go broadcast the update for each
+ // of the affected messages.
for (int i = 0; i < updatedMessages.size(); i++) {
- Message message = (Message)updatedMessages.get(i);
+ Message message = (Message)updatedMessages.get(i);
notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message);
}
}
-
+
/**
* Set flags on a range of messages to the supplied value.
* This method may be overridden by subclasses that can optimize the setting
* of flags on multiple messages at once; the default implementation simply
* gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
- *
+ *
* @param start first message end set
* @param end last message end set
* @param flags the set of flags end modify
* @param value Indicates whether the flags should be set or cleared.
- *
+ *
* @throws MessagingException
* if there was a problem accessing the store
*/
public synchronized void setFlags(int start, int end, Flags flags, boolean value) throws MessagingException {
- Message[] msgs = new Message[end - start + 1];
-
+ Message[] msgs = new Message[end - start + 1];
+
for (int i = start; i <= end; i++) {
msgs[i] = getMessage(i);
}
- // go do a bulk set operation on these messages
- setFlags(msgs, flags, value);
+ // go do a bulk set operation on these messages
+ setFlags(msgs, flags, value);
}
/**
@@ -1160,25 +1146,25 @@
* This method may be overridden by subclasses that can optimize the setting
* of flags on multiple messages at once; the default implementation simply
* gets each message and then calls {@link Message#setFlags(Flags, boolean)}.
- *
+ *
* @param ids the indexes of the messages to set
* @param flags the set of flags end modify
* @param value Indicates whether the flags should be set or cleared.
- *
+ *
* @throws MessagingException
* if there was a problem accessing the store
*/
public synchronized void setFlags(int ids[], Flags flags, boolean value) throws MessagingException {
- Message[] msgs = new Message[ids.length];
-
+ Message[] msgs = new Message[ids.length];
+
for (int i = 0; i <ids.length; i++) {
msgs[i] = getMessage(ids[i]);
}
- // go do a bulk set operation on these messages
- setFlags(msgs, flags, value);
+ // go do a bulk set operation on these messages
+ setFlags(msgs, flags, value);
}
-
+
/**
* Copy the specified messages to another folder.
* The default implementation simply appends the supplied messages to the
@@ -1188,14 +1174,14 @@
* @throws MessagingException if there was a problem accessing the store
*/
public synchronized void copyMessages(Message[] messages, Folder folder) throws MessagingException {
- // the default implementation just appends the messages to the target. If
- // we're copying between two folders of the same store, we can get the server to
- // do most of the work for us without needing to fetch all of the message data.
- // If we're dealing with two different Store instances, we need to do this the
+ // the default implementation just appends the messages to the target. If
+ // we're copying between two folders of the same store, we can get the server to
+ // do most of the work for us without needing to fetch all of the message data.
+ // If we're dealing with two different Store instances, we need to do this the
// hardway.
if (getStore() != folder.getStore()) {
- super.copyMessages(messages, folder);
- return;
+ super.copyMessages(messages, folder);
+ return;
}
// turn this into a set of message numbers
@@ -1205,17 +1191,17 @@
return;
}
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
- // ask the server to copy this information over to the other mailbox.
- connection.copyMessages(messageSet, folder.getFullName());
+ // ask the server to copy this information over to the other mailbox.
+ connection.copyMessages(messageSet, folder.getFullName());
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
-
+
/**
* Permanently delete all supplied messages that have the DELETED flag set from the Store.
@@ -1233,7 +1219,7 @@
checkReadWrite();
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
List expunges = null;
try {
@@ -1243,22 +1229,22 @@
// expunge messages that also marked messages as expunged.
expunges = connection.expungeMailbox();
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
- // we get one EXPUNGE message for each message that's expunged. They MUST be processed in
- // order, as the message sequence numbers represent a relative position that takes into account
- // previous expunge operations. For example, if message sequence numbers 5, 6, and 7 are
- // expunged, we receive 3 expunge messages, all indicating that message 5 has been expunged.
- Message[] messages = new Message[expunges.size()];
+ // we get one EXPUNGE message for each message that's expunged. They MUST be processed in
+ // order, as the message sequence numbers represent a relative position that takes into account
+ // previous expunge operations. For example, if message sequence numbers 5, 6, and 7 are
+ // expunged, we receive 3 expunge messages, all indicating that message 5 has been expunged.
+ Message[] messages = new Message[expunges.size()];
// now we need to protect the internal structures
synchronized (this) {
- // expunge all of the messages from the message cache. This keeps the sequence
- // numbers up to-date.
+ // expunge all of the messages from the message cache. This keeps the sequence
+ // numbers up to-date.
for (int i = 0; i < expunges.size(); i++) {
- IMAPSizeResponse response = (IMAPSizeResponse)expunges.get(i);
- messages[i] = expungeMessage(response.getSize());
+ IMAPSizeResponse response = (IMAPSizeResponse)expunges.get(i);
+ messages[i] = expungeMessage(response.getSize());
}
}
// if we have messages that have been removed, broadcast the notification.
@@ -1289,14 +1275,14 @@
checkOpen();
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
// just search everything
int[] messageNumbers = connection.searchMailbox(term);
return resolveMessages(messageNumbers);
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
@@ -1328,7 +1314,7 @@
}
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
@@ -1336,31 +1322,31 @@
int[] messageNumbers = connection.searchMailbox(messageSet, term);
return resolveMessages(messageNumbers);
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
/**
* Get the UID validity value for this Folder.
- *
- * @return The current UID validity value, as a long.
+ *
+ * @return The current UID validity value, as a long.
* @exception MessagingException
*/
public synchronized long getUIDValidity() throws MessagingException
{
- // get the latest status to make sure we have the
- // most current.
- refreshStatus(true);
- return uidValidity;
+ // get the latest status to make sure we have the
+ // most current.
+ refreshStatus(true);
+ return uidValidity;
}
/**
- * Retrieve a message using the UID rather than the
+ * Retrieve a message using the UID rather than the
* message sequence number. Returns null if the message
* doesn't exist.
- *
+ *
* @param uid The target UID.
- *
+ *
* @return the Message object. Returns null if the message does
* not exist.
* @exception MessagingException
@@ -1369,46 +1355,46 @@
{
// only allowed on open folders
checkOpen();
-
- Long key = new Long(uid);
- // first check to see if we have a cached value for this
- synchronized(messages) {
- Message msg = (Message)uidCache.get(key);
+
+ Long key = new Long(uid);
+ // first check to see if we have a cached value for this
+ synchronized(messageCache) {
+ Message msg = (Message)uidCache.get(key);
if (msg != null) {
- return msg;
+ return msg;
}
}
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
- // locate the message identifier
+ // locate the message identifier
IMAPUid imapuid = connection.getSequenceNumberForUid(uid);
- // if nothing is returned, the message doesn't exist
+ // if nothing is returned, the message doesn't exist
if (imapuid == null) {
- return null;
+ return null;
}
-
-
+
+
// retrieve the actual message object and place this in the UID cache
- return retrieveMessageByUid(key, imapuid.messageNumber);
+ return retrieveMessageByUid(key, imapuid.messageNumber);
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
/**
- * Get a series of messages using a UID range. The
- * special value LASTUID can be used to mark the
+ * Get a series of messages using a UID range. The
+ * special value LASTUID can be used to mark the
* last available message.
- *
+ *
* @param start The start of the UID range.
* @param end The end of the UID range. The special value
* LASTUID can be used to request all messages up
* to the last UID.
- *
- * @return An array containing all of the messages in the
+ *
+ * @return An array containing all of the messages in the
* range.
* @exception MessagingException
*/
@@ -1417,34 +1403,34 @@
// only allowed on open folders
checkOpen();
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
- // locate the message identifier
+ // locate the message identifier
List uids = connection.getSequenceNumbersForUids(start, end);
Message[] msgs = new Message[uids.size()];
-
- // fill in each of the messages based on the returned value
+
+ // fill in each of the messages based on the returned value
for (int i = 0; i < msgs.length; i++) {
- IMAPUid uid = (IMAPUid)uids.get(i);
+ IMAPUid uid = (IMAPUid)uids.get(i);
msgs[i] = retrieveMessageByUid(new Long(uid.uid), uid.messageNumber);
}
-
- return msgs;
+
+ return msgs;
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
-
-
+
+
}
/**
- * Retrieve a set of messages by explicit UIDs. If
- * any message in the list does not exist, null
+ * Retrieve a set of messages by explicit UIDs. If
+ * any message in the list does not exist, null
* will be returned for the corresponding item.
- *
+ *
* @param ids An array of UID values to be retrieved.
- *
+ *
* @return An array of Message items the same size as the ids
* argument array. This array will contain null
* entries for any UIDs that do not exist.
@@ -1454,134 +1440,134 @@
{
// only allowed on open folders
checkOpen();
-
+
Message[] msgs = new Message[ids.length];
-
+
for (int i = 0; i < msgs.length; i++) {
- msgs[i] = getMessageByUID(ids[i]);
+ msgs[i] = getMessageByUID(ids[i]);
}
-
- return msgs;
+
+ return msgs;
}
/**
* Retrieve the UID for a message from this Folder.
* The argument Message MUST belong to this Folder
- * instance, otherwise a NoSuchElementException will
+ * instance, otherwise a NoSuchElementException will
* be thrown.
- *
+ *
* @param message The target message.
- *
+ *
* @return The UID associated with this message.
* @exception MessagingException
*/
public synchronized long getUID(Message message) throws MessagingException
{
- // verify this actually is in this folder.
- checkMessageFolder(message);
- IMAPMessage msg = (IMAPMessage)message;
-
- // we might already know this bit of information
+ // verify this actually is in this folder.
+ checkMessageFolder(message);
+ IMAPMessage msg = (IMAPMessage)message;
+
+ // we might already know this bit of information
if (msg.getUID() != -1) {
- return msg.getUID();
+ return msg.getUID();
}
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
- // locate the message identifier
+ // locate the message identifier
IMAPUid imapuid = connection.getUidForSequenceNumber(msg.getMessageNumber());
- // if nothing is returned, the message doesn't exist
+ // if nothing is returned, the message doesn't exist
if (imapuid == null) {
return -1;
}
- // cache this information now that we've gotten it.
+ // cache this information now that we've gotten it.
addToUidCache(new Long(imapuid.uid), getMessage(imapuid.messageNumber));
- // return the UID information.
- return imapuid.uid;
+ // return the UID information.
+ return imapuid.uid;
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
-
+
/**
* Retrieve a message from a UID/message mapping.
- *
+ *
* @param key The UID key used for the mapping.
* @param msgNumber The message sequence number.
- *
+ *
* @return The Message object corresponding to the message.
* @exception MessagingException
*/
protected synchronized Message retrieveMessageByUid(Long key, int msgNumber) throws MessagingException
{
- synchronized (messages) {
- // first check the cache...this might have already been added.
- Message msg = (Message)uidCache.get(key);
+ synchronized (messageCache) {
+ // first check the cache...this might have already been added.
+ Message msg = (Message)uidCache.get(key);
if (msg != null) {
- return msg;
+ return msg;
}
-
- // retrieve the message by sequence number
- msg = getMessage(msgNumber);
- // add this to our UID mapping cache.
- addToUidCache(key, msg);
- return msg;
- }
- }
-
-
- /**
- * Add a message to the UID mapping cache, ensuring that
- * the UID value is updated.
- *
- * @param key The UID key.
- * @param msg The message to add.
- */
- protected void addToUidCache(Long key, Message msg) {
- synchronized (messages) {
- ((IMAPMessage)msg).setUID(key.longValue());
- uidCache.put(key, msg);
- }
- }
-
-
- /**
- * Append a single message to the IMAP Folder.
- *
- * @param msg The message to append.
- *
- * @exception MessagingException
- */
- protected synchronized void appendMessage(Message msg) throws MessagingException
- {
- // sort out the dates. If no received date, use the sent date.
- Date date = msg.getReceivedDate();
- if (date == null) {
- date = msg.getSentDate();
- }
-
- Flags flags = msg.getFlags();
-
- // convert the message into an array of bytes we can attach as a literal.
- ByteArrayOutputStream out = new ByteArrayOutputStream();
-
- try {
- msg.writeTo(out);
- } catch (IOException e) {
- }
-
- // now issue the append command
- IMAPConnection connection = getConnection();
- try {
- connection.appendMessage(getFullName(), date, flags, out.toByteArray());
- } finally {
- releaseConnection(connection);
+
+ // retrieve the message by sequence number
+ msg = getMessage(msgNumber);
+ // add this to our UID mapping cache.
+ addToUidCache(key, msg);
+ return msg;
}
}
-
+
+ /**
+ * Add a message to the UID mapping cache, ensuring that
+ * the UID value is updated.
+ *
+ * @param key The UID key.
+ * @param msg The message to add.
+ */
+ protected void addToUidCache(Long key, Message msg) {
+ synchronized (messageCache) {
+ ((IMAPMessage)msg).setUID(key.longValue());
+ uidCache.put(key, msg);
+ }
+ }
+
+
+ /**
+ * Append a single message to the IMAP Folder.
+ *
+ * @param msg The message to append.
+ *
+ * @exception MessagingException
+ */
+ protected synchronized void appendMessage(Message msg) throws MessagingException
+ {
+ // sort out the dates. If no received date, use the sent date.
+ Date date = msg.getReceivedDate();
+ if (date == null) {
+ date = msg.getSentDate();
+ }
+
+ Flags flags = msg.getFlags();
+
+ // convert the message into an array of bytes we can attach as a literal.
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ try {
+ msg.writeTo(out);
+ } catch (IOException e) {
+ }
+
+ // now issue the append command
+ IMAPConnection connection = getConnection();
+ try {
+ connection.appendMessage(getFullName(), date, flags, out.toByteArray());
+ } finally {
+ releaseConnection(connection);
+ }
+ }
+
+
/**
* Retrieve the list of matching groups from the IMAP server using the LIST
* or LSUB command. The server does the wildcard matching for us.
@@ -1592,12 +1578,12 @@
* @return An array of folders for the matching groups.
*/
protected synchronized Folder[] filterFolders(String pattern, boolean subscribed) throws MessagingException {
- IMAPConnection connection = getConnection();
- // this is used to filter out our own folder from the search
+ IMAPConnection connection = getConnection();
+ // this is used to filter out our own folder from the search
String root = fullname + getSeparator();
-
+
List responses = null;
- try {
+ try {
if (subscribed) {
@@ -1609,7 +1595,7 @@
responses = connection.list(root, pattern);
}
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
List folders = new ArrayList();
@@ -1660,8 +1646,8 @@
if (messageNumber <= maxSequenceNumber) {
return;
}
-
- IMAPConnection connection = getConnection();
+
+ IMAPConnection connection = getConnection();
synchronized (this) {
try {
@@ -1669,7 +1655,7 @@
// by the response handlers.
connection.updateMailboxStatus();
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
@@ -1688,7 +1674,7 @@
/**
* Ensure the folder is open. Throws a MessagingException
* if not in the correct state for the operation.
- *
+ *
* @exception IllegalStateException
*/
protected void checkOpen() throws IllegalStateException {
@@ -1700,7 +1686,7 @@
/**
* Ensure the folder is not open for operations
* that require the folder to be closed.
- *
+ *
* @exception IllegalStateException
*/
protected void checkClosed() throws IllegalStateException {
@@ -1735,19 +1721,19 @@
/**
- * Notify the message changed listeners that a
+ * Notify the message changed listeners that a
* message contained in the folder has been updated.
- *
+ *
* @param type The type of update made to the message.
* @param m The message that was updated.
- *
+ *
* @see javax.mail.Folder#notifyMessageChangedListeners(int, javax.mail.Message)
*/
public void notifyMessageChangedListeners(int type, Message m) {
super.notifyMessageChangedListeners(type, m);
}
-
+
/**
* Retrieve the connection attached to this folder. Throws an
* exception if we don't have an active connection.
@@ -1756,115 +1742,115 @@
* @exception MessagingException
*/
protected synchronized IMAPConnection getConnection() throws MessagingException {
- // don't have an open connection yet? Just request a pool connection.
+ // don't have an open connection yet? Just request a pool connection.
if (currentConnection == null) {
- // request a connection from the central store.
- IMAPConnection connection = ((IMAPStore)store).getFolderConnection(this);
- // we need to make ourselves a handler of unsolicited responses
- connection.addResponseHandler(this);
+ // request a connection from the central store.
+ IMAPConnection connection = ((IMAPStore)store).getFolderConnection(this);
+ // we need to make ourselves a handler of unsolicited responses
+ connection.addResponseHandler(this);
return connection;
}
- // we have a connection for our use. Just return it.
- return currentConnection;
+ // we have a connection for our use. Just return it.
+ return currentConnection;
}
-
-
+
+
/**
* Release our connection back to the Store.
- *
+ *
* @param connection The connection to release.
- *
+ *
* @exception MessagingException
*/
protected void releaseConnection(IMAPConnection connection) throws MessagingException {
- // This is a bit of a pain. We need to delay processing of the
- // unsolicited responses until after each user of the connection has
- // finished processing the expected responses. We need to do this because
- // the unsolicited responses may include EXPUNGED messages. The EXPUNGED
- // messages will alter the message sequence numbers for the messages in the
- // cache. Processing the EXPUNGED messages too early will result in
- // updates getting applied to the wrong message instances. So, as a result,
- // we delay that stage of the processing until all expected responses have
- // been handled.
-
- // process any pending messages before returning.
- connection.processPendingResponses();
- // if no cached connection or this is somehow different from the cached one, just
- // return it.
+ // This is a bit of a pain. We need to delay processing of the
+ // unsolicited responses until after each user of the connection has
+ // finished processing the expected responses. We need to do this because
+ // the unsolicited responses may include EXPUNGED messages. The EXPUNGED
+ // messages will alter the message sequence numbers for the messages in the
+ // cache. Processing the EXPUNGED messages too early will result in
+ // updates getting applied to the wrong message instances. So, as a result,
+ // we delay that stage of the processing until all expected responses have
+ // been handled.
+
+ // process any pending messages before returning.
+ connection.processPendingResponses();
+ // if no cached connection or this is somehow different from the cached one, just
+ // return it.
if (currentConnection == null || connection != currentConnection) {
- connection.removeResponseHandler(this);
- ((IMAPStore)store).releaseFolderConnection(this, connection);
+ connection.removeResponseHandler(this);
+ ((IMAPStore)store).releaseFolderConnection(this, connection);
}
- // if we're open, then we don't have to worry about returning this connection
- // to the Store. This is set up perfectly for our use right now.
+ // if we're open, then we don't have to worry about returning this connection
+ // to the Store. This is set up perfectly for our use right now.
}
-
-
+
+
/**
- * Obtain a connection object for a Message attached to this Folder. This
- * will be the Folder's connection, which is only available if the Folder
+ * Obtain a connection object for a Message attached to this Folder. This
+ * will be the Folder's connection, which is only available if the Folder
* is currently open.
- *
- * @return The connection object for the Message instance to use.
+ *
+ * @return The connection object for the Message instance to use.
* @exception MessagingException
*/
synchronized IMAPConnection getMessageConnection() throws MessagingException {
// if we're not open, the messages can't communicate either
if (currentConnection == null) {
- throw new FolderClosedException(this, "No Folder connections available");
+ throw new FolderClosedException(this, "No Folder connections available");
}
- // return the current Folder connection. At this point, we'll be sharing the
- // connection between the Folder and the Message (and potentially, other messages). The
- // command operations on the connection are synchronized so only a single command can be
- // issued at one time.
- return currentConnection;
+ // return the current Folder connection. At this point, we'll be sharing the
+ // connection between the Folder and the Message (and potentially, other messages). The
+ // command operations on the connection are synchronized so only a single command can be
+ // issued at one time.
+ return currentConnection;
}
-
-
+
+
/**
- * Release the connection object back to the Folder instance.
- *
+ * Release the connection object back to the Folder instance.
+ *
* @param connection The connection being released.
- *
+ *
* @exception MessagingException
*/
void releaseMessageConnection(IMAPConnection connection) throws MessagingException {
- // release it back to ourselves...this will drive unsolicited message processing.
- releaseConnection(connection);
+ // release it back to ourselves...this will drive unsolicited message processing.
+ releaseConnection(connection);
}
/**
* Refresh the status information on this folder.
- *
+ *
* @param force Force a status refresh always.
- *
+ *
* @exception MessagingException
*/
protected void refreshStatus(boolean force) throws MessagingException {
// first check that any cached status we've received has gotten a little moldy.
if (cachedStatus != null) {
- // if not forcing, check the time out.
+ // if not forcing, check the time out.
if (!force) {
if (statusCacheTimeout > 0) {
long age = System.currentTimeMillis() - lastStatusTimeStamp;
if (age < statusCacheTimeout) {
- return;
+ return;
}
}
}
// make sure the stale information is cleared out.
cachedStatus = null;
}
-
- IMAPConnection connection = getConnection();
+
+ IMAPConnection connection = getConnection();
try {
// ping the server for the list information for this folder
cachedStatus = connection.getMailboxStatus(fullname);
// mark when we got this
lastStatusTimeStamp = System.currentTimeMillis();
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
// refresh the internal state from the message information
@@ -1878,42 +1864,64 @@
/**
* Process an EXPUNGE response for a message, removing the
* message from the message cache.
- *
+ *
* @param sequenceNumber
* The sequence number for the expunged message.
- *
+ *
* @return The Message object corresponding to this expunged
* message.
* @exception MessagingException
*/
protected synchronized Message expungeMessage(int sequenceNumber) throws MessagingException {
- // we need to remove this from our message list. Deletion of a message by
- // sequence number shifts the sequence numbers of every message after
- // the target. So we, need to remove the message, update its status, then
- // update the sequence numbers of every message after that. This is a serious
- // pain!
-
- Message expungedMessage = (Message)messages.remove(sequenceNumber - 1);
- // mark the message as expunged.
- ((IMAPMessage)expungedMessage).setExpunged(true);
-
- // update the message sequence numbers for every message after the
- // expunged one. NB. The sequence number is the cache index + 1
- for (int i = sequenceNumber - 1; i < messages.size(); i++) {
- IMAPMessage message = (IMAPMessage)messages.get(i);
- message.setSequenceNumber(i + 1);
- }
-
+
+ // first process the expunged message. We need to return a Message instance, so
+ // force this to be added to the cache
+ IMAPMessage expungedMessage = (IMAPMessage)getMessage(sequenceNumber);
+ // mark the message as expunged.
+ expungedMessage.setExpunged(true);
// have we retrieved a UID for this message? If we have, then it's in the UID cache and
// needs removal from there also
long uid = ((IMAPMessage)expungedMessage).getUID();
if (uid >= 0) {
uidCache.remove(new Long(uid));
}
+ // because we need to jigger the keys of some of these, we had better have a working
+ // copy.
+ Map newCache = new HashMap();
+
+ // now process each message in the cache, making adjustments as necessary
+ Iterator i = messageCache.keySet().iterator();
+
+ while (i.hasNext()) {
+ Integer key = (Integer)i.next();
+ int index = key.intValue();
+ // if before the expunged message, just copy over to the
+ // new cache
+ if (index < sequenceNumber) {
+ newCache.put(key, messageCache.get(key));
+ }
+ // after the expunged message...we need to adjust this
+ else if (index > sequenceNumber) {
+ // retrieve the message using the current position,
+ // adjust the message sequence number, and add to the new
+ // message cache under the new key value
+ IMAPMessage message = (IMAPMessage)messageCache.get(key);
+ message.setSequenceNumber(index - 1);
+ newCache.put(new Integer(index - 1), message);
+ }
+ else {
+ // the expunged message. We don't move this over to the new
+ // cache, and we've already done all processing of that message that's
+ // required
+ }
+ }
+
+ // replace the old cache now that everything has been adjusted
+ messageCache = newCache;
// adjust the message count downward
maxSequenceNumber--;
- return expungedMessage;
+ return expungedMessage;
}
@@ -1945,19 +1953,19 @@
return messages;
}
-
+
/**
- * Generate a message set string from a List of messages rather than an
+ * Generate a message set string from a List of messages rather than an
* array.
- *
+ *
* @param messages The List of messages.
- *
- * @return The evaluated message set string.
+ *
+ * @return The evaluated message set string.
* @exception MessagingException
*/
protected String generateMessageSet(List messages) throws MessagingException {
- Message[] msgs = (Message[])messages.toArray(new Message[messages.size()]);
- return generateMessageSet(msgs);
+ Message[] msgs = (Message[])messages.toArray(new Message[messages.size()]);
+ return generateMessageSet(msgs);
}
@@ -1996,8 +2004,8 @@
set.append(',');
}
- // append the first number. NOTE: We append this directly rather than
- // use appendInteger(), which appends it using atom rules.
+ // append the first number. NOTE: We append this directly rather than
+ // use appendInteger(), which appends it using atom rules.
set.append(Integer.toString(start.getSequenceNumber()));
// ok, we have a live one. Now scan the list from here looking for the end of
@@ -2041,37 +2049,37 @@
}
return set.toString();
}
-
+
/**
- * Verify that this folder exists on the server before
- * performning an operation that requires a valid
- * Folder instance.
- *
+ * Verify that this folder exists on the server before
+ * performning an operation that requires a valid
+ * Folder instance.
+ *
* @exception MessagingException
*/
protected void checkFolderValidity() throws MessagingException {
- // if we are holding a current listinfo response, then
- // we have chached existance information. In that case,
- // all of our status is presumed up-to-date and we can go
- // with that. If we don't have the information, then we
- // ping the server for it.
+ // if we are holding a current listinfo response, then
+ // we have chached existance information. In that case,
+ // all of our status is presumed up-to-date and we can go
+ // with that. If we don't have the information, then we
+ // ping the server for it.
if (listInfo == null && !exists()) {
- throw new FolderNotFoundException(this, "Folder " + fullname + " not found on server");
+ throw new FolderNotFoundException(this, "Folder " + fullname + " not found on server");
}
}
-
-
+
+
/**
- * Check if a Message is properly within the target
- * folder.
- *
+ * Check if a Message is properly within the target
+ * folder.
+ *
* @param msg The message we're checking.
- *
+ *
* @exception MessagingException
*/
protected void checkMessageFolder(Message msg) throws MessagingException {
if (msg.getFolder() != this) {
- throw new NoSuchElementException("Message is not within the target Folder");
+ throw new NoSuchElementException("Message is not within the target Folder");
}
}
@@ -2094,293 +2102,292 @@
}
return null;
}
-
-
+
+
/**
- * Protected class intended for subclass overrides. For normal folders,
+ * Protected class intended for subclass overrides. For normal folders,
* the mailbox name is fullname. For Namespace root folders, the mailbox
- * name is the prefix + separator.
- *
- * @return The string name to use as the mailbox name for exists() and issubscribed()
+ * name is the prefix + separator.
+ *
+ * @return The string name to use as the mailbox name for exists() and issubscribed()
* calls.
*/
protected String getMailBoxName() {
- return fullname;
+ return fullname;
}
-
+
/**
- * Handle an unsolicited response from the server. Most unsolicited responses
- * are replies to specific commands sent to the server. The remainder must
- * be handled by the Store or the Folder using the connection. These are
- * critical to handle, as events such as expunged messages will alter the
+ * Handle an unsolicited response from the server. Most unsolicited responses
+ * are replies to specific commands sent to the server. The remainder must
+ * be handled by the Store or the Folder using the connection. These are
+ * critical to handle, as events such as expunged messages will alter the
* sequence numbers of the live messages. We need to keep things in sync.
- *
+ *
* @param response The UntaggedResponse to process.
- *
+ *
* @return true if we handled this response and no further handling is required. false
* means this one wasn't one of ours.
*/
public boolean handleResponse(IMAPUntaggedResponse response) {
- // "you've got mail". The message count has been updated. There
- // are two posibilities. Either there really are new messages, or
- // this is an update following an expunge. If there are new messages,
- // we need to update the message cache and broadcast the change to
+ // "you've got mail". The message count has been updated. There
+ // are two posibilities. Either there really are new messages, or
+ // this is an update following an expunge. If there are new messages,
+ // we need to update the message cache and broadcast the change to
// any listeners.
if (response.isKeyword("EXISTS")) {
- // we need to update our cache, and also retrieve the new messages and
- // send them out in a broadcast update.
- int oldCount = maxSequenceNumber;
- maxSequenceNumber = ((IMAPSizeResponse)response).getSize();
- populateMessageCache();
- // has the size grown? We have to send the "you've got mail" announcement.
+ // we need to update our cache, and also retrieve the new messages and
+ // send them out in a broadcast update.
+ int oldCount = maxSequenceNumber;
+ maxSequenceNumber = ((IMAPSizeResponse)response).getSize();
+ // has the size grown? We have to send the "you've got mail" announcement.
if (oldCount < maxSequenceNumber) {
try {
- Message[] messages = getMessages(oldCount + 1, maxSequenceNumber);
- notifyMessageAddedListeners(messages);
+ Message[] messages = getMessages(oldCount + 1, maxSequenceNumber);
+ notifyMessageAddedListeners(messages);
} catch (MessagingException e) {
- // should never happen in this context
+ // should never happen in this context
}
}
- return true;
+ return true;
}
- // "you had mail". A message was expunged from the server. This MUST
- // be processed immediately, as any subsequent expunge messages will
- // shift the message numbers as a result of previous messages getting
- // removed. We need to keep our internal cache in sync with the server.
+ // "you had mail". A message was expunged from the server. This MUST
+ // be processed immediately, as any subsequent expunge messages will
+ // shift the message numbers as a result of previous messages getting
+ // removed. We need to keep our internal cache in sync with the server.
else if (response.isKeyword("EXPUNGE")) {
- int messageNumber = ((IMAPSizeResponse)response).getSize();
+ int messageNumber = ((IMAPSizeResponse)response).getSize();
try {
- Message message = expungeMessage(messageNumber);
+ Message message = expungeMessage(messageNumber);
- // broadcast the message update.
- notifyMessageRemovedListeners(false, new Message[] {message});
+ // broadcast the message update.
+ notifyMessageRemovedListeners(false, new Message[] {message});
} catch (MessagingException e) {
}
- // we handled this one.
- return true;
+ // we handled this one.
+ return true;
}
- // just an update of recently arrived stuff? Just update the field.
+ // just an update of recently arrived stuff? Just update the field.
else if (response.isKeyword("RECENT")) {
- recentMessages = ((IMAPSizeResponse)response).getSize();
- return true;
+ recentMessages = ((IMAPSizeResponse)response).getSize();
+ return true;
}
- // The spec is not particularly clear what types of unsolicited
- // FETCH response can be sent. The only one that is specifically
- // spelled out is flag updates. If this is one of those, then
- // handle it.
+ // The spec is not particularly clear what types of unsolicited
+ // FETCH response can be sent. The only one that is specifically
+ // spelled out is flag updates. If this is one of those, then
+ // handle it.
else if (response.isKeyword("FETCH")) {
- IMAPFetchResponse fetch = (IMAPFetchResponse)response;
- IMAPFlags flags = (IMAPFlags)fetch.getDataItem(IMAPFetchDataItem.FLAGS);
- // if this is a flags response, get the message and update
+ IMAPFetchResponse fetch = (IMAPFetchResponse)response;
+ IMAPFlags flags = (IMAPFlags)fetch.getDataItem(IMAPFetchDataItem.FLAGS);
+ // if this is a flags response, get the message and update
if (flags != null) {
try {
- // get the updated message and update the internal state.
- IMAPMessage message = (IMAPMessage)getMessage(fetch.sequenceNumber);
- // this shouldn't happen, but it might have been expunged too.
+ // get the updated message and update the internal state.
+ IMAPMessage message = (IMAPMessage)getMessage(fetch.sequenceNumber);
+ // this shouldn't happen, but it might have been expunged too.
if (message != null) {
- message.updateMessageInformation(fetch);
+ message.updateMessageInformation(fetch);
}
notifyMessageChangedListeners(MessageChangedEvent.FLAGS_CHANGED, message);
} catch (MessagingException e) {
}
- return true;
+ return true;
}
}
- // this is a BYE response on our connection. This forces us to close, but
- // when we return the connection, the pool needs to get rid of it.
+ // this is a BYE response on our connection. This forces us to close, but
+ // when we return the connection, the pool needs to get rid of it.
else if (response.isKeyword("BYE")) {
- // this is essentially a close event. We need to clean everything up
- // and make sure our connection is not returned to the general pool.
+ // this is essentially a close event. We need to clean everything up
+ // and make sure our connection is not returned to the general pool.
try {
- cleanupFolder(false, true);
+ cleanupFolder(false, true);
} catch (MessagingException e) {
}
- return true;
+ return true;
}
-
- // not a response the folder knows how to deal with.
- return false;
+
+ // not a response the folder knows how to deal with.
+ return false;
}
-
-// The following set of methods are extensions that exist in the Sun implementation. They
-// match the Sun version in intent, but are not 100% compatible because the Sun implementation
-// uses com.sun.* class instances as opposed to the org.apache.geronimo.* classes.
-
-
-
+
+// The following set of methods are extensions that exist in the Sun implementation. They
+// match the Sun version in intent, but are not 100% compatible because the Sun implementation
+// uses com.sun.* class instances as opposed to the org.apache.geronimo.* classes.
+
+
+
/**
* Remove an entry from the access control list for this folder.
- *
+ *
* @param acl The ACL element to remove.
- *
+ *
* @exception MessagingException
*/
public synchronized void removeACL(ACL acl) throws MessagingException {
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
- // the connection does the heavy lifting
- connection.removeACLRights(fullname, acl);
+ // the connection does the heavy lifting
+ connection.removeACLRights(fullname, acl);
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
-
-
+
+
/**
* Add an entry to the access control list for this folder.
- *
+ *
* @param acl The new ACL to add.
*/
public synchronized void addACL(ACL acl) throws MessagingException {
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
- // the connection does the heavy lifting
- connection.setACLRights(fullname, acl);
+ // the connection does the heavy lifting
+ connection.setACLRights(fullname, acl);
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
-
-
+
+
/**
* Add Rights to a given ACL entry.
- *
+ *
* @param acl The target ACL to update.
- *
+ *
* @exception MessagingException
*/
public synchronized void addRights(ACL acl) throws MessagingException {
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
- // the connection does the heavy lifting
- connection.addACLRights(fullname, acl);
+ // the connection does the heavy lifting
+ connection.addACLRights(fullname, acl);
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
-
-
+
+
/**
* Remove ACL Rights from a folder.
- *
+ *
* @param acl The ACL describing the Rights to remove.
- *
+ *
* @exception MessagingException
*/
public synchronized void removeRights(ACL acl) throws MessagingException {
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
- // the connection does the heavy lifting
- connection.removeACLRights(fullname, acl);
+ // the connection does the heavy lifting
+ connection.removeACLRights(fullname, acl);
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
-
-
+
+
/**
* List the rights associated with a given name.
- *
+ *
* @param name The user name for the Rights.
- *
+ *
* @return The set of Rights associated with the user name.
* @exception MessagingException
*/
public synchronized Rights[] listRights(String name) throws MessagingException {
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
- // the connection does the heavy lifting
- return connection.listACLRights(fullname, name);
+ // the connection does the heavy lifting
+ return connection.listACLRights(fullname, name);
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
-
-
+
+
/**
* List the rights for the currently authenticated user.
- *
+ *
* @return The set of Rights for the current user.
* @exception MessagingException
*/
public synchronized Rights myRights() throws MessagingException {
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
- // the connection does the heavy lifting
- return connection.getMyRights(fullname);
+ // the connection does the heavy lifting
+ return connection.getMyRights(fullname);
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
-
+
/**
* Get the quota values assigned to the current folder.
- *
+ *
* @return The Quota information for the folder.
* @exception MessagingException
*/
public synchronized Quota[] getQuota() throws MessagingException {
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
- // the connection does the heavy lifting
- return connection.fetchQuotaRoot(fullname);
+ // the connection does the heavy lifting
+ return connection.fetchQuotaRoot(fullname);
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
-
+
/**
* Set the quota value for a quota root
- *
+ *
* @param quota The new quota information to set.
- *
+ *
* @exception MessagingException
*/
public synchronized void setQuota(Quota quota) throws MessagingException {
// ask the store to kindly hook us up with a connection.
- IMAPConnection connection = getConnection();
+ IMAPConnection connection = getConnection();
try {
- // the connection does the heavy lifting
- connection.setQuota(quota);
+ // the connection does the heavy lifting
+ connection.setQuota(quota);
} finally {
- releaseConnection(connection);
+ releaseConnection(connection);
}
}
-
+
/**
* Get the set of attributes defined for the folder
- * as the set of capabilities returned when the folder
+ * as the set of capabilities returned when the folder
* was opened.
- *
+ *
* @return The set of attributes associated with the folder.
* @exception MessagingException
*/
public synchronized String[] getAttributes() throws MessagingException {
- // if we don't have the LIST command information for this folder yet,
- // call exists() to force this to be updated so we can return.
+ // if we don't have the LIST command information for this folder yet,
+ // call exists() to force this to be updated so we can return.
if (listInfo == null) {
- // return a null reference if this is not valid.
+ // return a null reference if this is not valid.
if (!exists()) {
- return null;
+ return null;
}
}
- // return a copy of the attributes array.
- return (String[])listInfo.attributes.clone();
+ // return a copy of the attributes array.
+ return (String[])listInfo.attributes.clone();
}
}