com.sun.mail.imap.IMAPFolder.idle Maven / Gradle / Ivy
diff -r bb4b8c17762e mail/src/main/java/com/sun/mail/imap/IMAPFolder.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java Fri Feb 07 16:35:37 2014 -0800
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java Mon Mar 17 14:37:41 2014 -0700
@@ -1,7 +1,7 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
- * Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997-2014 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
@@ -49,6 +49,7 @@
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.io.*;
+import java.nio.channels.SocketChannel;
import javax.mail.*;
import javax.mail.event.*;
@@ -264,6 +265,8 @@
private static final int IDLE = 1; // IDLE command in effect
private static final int ABORTING = 2; // IDLE command aborting
private int idleState = RUNNING;
+ private volatile IdleManager idleManager;
+ private volatile List idleEvents;
private volatile int total = -1; // total number of messages in the
// message cache
@@ -2259,6 +2262,86 @@
hasMessageCountListener = true;
}
+ // Override notification methods to accumulate IDLE events.
+
+ /**
+ * Notify all MessageCountListeners about the addition of messages
+ * into this folder. Folder implementations are expected to use this
+ * method to broadcast MessageCount events for indicating arrival of
+ * new messages.
+ *
+ * The provided implementation queues the event into
+ * an internal event queue. An event dispatcher thread dequeues
+ * events from the queue and dispatches them to the registered
+ * MessageCountListeners. Note that the event dispatching occurs
+ * in a separate thread, thus avoiding potential deadlock problems.
+ *
+ * @param msgs the messages that were added
+ */
+ @Override
+ protected void notifyMessageAddedListeners(Message[] msgs) {
+ if (idleEvents != null) {
+ MessageCountEvent e = new MessageCountEvent(
+ this,
+ MessageCountEvent.ADDED,
+ false,
+ msgs);
+ idleEvents.add(e);
+ }
+ super.notifyMessageAddedListeners(msgs);
+ }
+
+ /**
+ * Notify all MessageCountListeners about the removal of messages
+ * from this Folder. Folder implementations are expected to use this
+ * method to broadcast MessageCount events indicating removal of
+ * messages.
+ *
+ * The provided implementation queues the event into
+ * an internal event queue. An event dispatcher thread dequeues
+ * events from the queue and dispatches them to the registered
+ * MessageCountListeners. Note that the event dispatching occurs
+ * in a separate thread, thus avoiding potential deadlock problems.
+ *
+ * @param removed was the message removed by this client?
+ * @param msgs the messages that were removed
+ */
+ @Override
+ protected void notifyMessageRemovedListeners(boolean removed,
+ Message[] msgs) {
+ if (idleEvents != null) {
+ MessageCountEvent e = new MessageCountEvent(
+ this,
+ MessageCountEvent.REMOVED,
+ removed,
+ msgs);
+ idleEvents.add(e);
+ }
+ super.notifyMessageRemovedListeners(removed, msgs);
+ }
+
+ /**
+ * Notify all MessageChangedListeners. Folder implementations are
+ * expected to use this method to broadcast MessageChanged events.
+ *
+ * The provided implementation queues the event into
+ * an internal event queue. An event dispatcher thread dequeues
+ * events from the queue and dispatches them to registered
+ * MessageChangedListeners. Note that the event dispatching occurs
+ * in a separate thread, thus avoiding potential deadlock problems.
+ *
+ * @param type the MessageChangedEvent type
+ * @param msg the message that changed
+ */
+ @Override
+ protected void notifyMessageChangedListeners(int type, Message msg) {
+ if (idleEvents != null) {
+ MessageChangedEvent e = new MessageChangedEvent(this, type, msg);
+ idleEvents.add(e);
+ }
+ super.notifyMessageChangedListeners(type, msg);
+ }
+
/***********************************************************
* UIDFolder interface methods
**********************************************************/
@@ -2855,6 +2938,67 @@
* @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");
+ }
+ startIdle(null);
+
+ /*
+ * 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) { }
+ }
+ }
+
+ /**
+ * Like {@link #idle}, but if once
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
+ */
+ private void XXXidle(boolean once) throws MessagingException {
// ASSERT: Must NOT be called with this folder's
// synchronization lock held.
assert !Thread.holdsLock(this);
@@ -2943,6 +3087,134 @@
}
}
+ /**
+ * 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.
+ *
+ * @exception MessagingException if the server doesn't support the
+ * IDLE extension
+ * @exception IllegalStateException if the folder isn't open
+ * @since JavaMail 1.5.2
+ */
+ void startIdle(IdleManager im) throws MessagingException {
+ // ASSERT: Must NOT be called with this folder's
+ // synchronization lock held.
+ assert !Thread.holdsLock(this);
+ idleManager = im;
+ synchronized(this) {
+ checkOpened();
+ Boolean started = (Boolean)doOptionalCommand("IDLE not supported",
+ new ProtocolCommand() {
+ public Object doCommand(IMAPProtocol p)
+ throws ProtocolException {
+ if (idleState == RUNNING) {
+ p.idleStart();
+ idleState = IDLE;
+ 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) { }
+ return Boolean.FALSE;
+ }
+ }
+ });
+ if (!started.booleanValue())
+ return;
+ }
+ }
+
+ /**
+ * 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 = protocol.readIdleResponse();
+ try {
+ synchronized (messageCacheLock) {
+ try {
+ if (r == null || protocol == null ||
+ !protocol.processIdleResponse(r)) {
+ idleState = RUNNING;
+ messageCacheLock.notifyAll();
+ return false; // done
+ }
+ } catch (ProtocolException pex) {
+ idleState = RUNNING;
+ messageCacheLock.notifyAll();
+ throw pex; // also done
+ }
+ if (once) {
+ if (idleState == IDLE) {
+ protocol.idleAbort();
+ idleState = ABORTING;
+ }
+ }
+ }
+ } catch (ConnectionException cex) {
+ // Oops, the store or folder died on us.
+ throwClosedException(cex);
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ }
+ return true;
+ }
+
+ /**
+ * 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).
+ *
+ * @return a list of events if we should look for more IDLE responses,
+ * null if IDLE is done
+ * @exception MessagingException for errors
+ * @since JavaMail 1.5.2
+ */
+ List handleIdleEvents() throws MessagingException {
+ List ret = null;
+ Response r = protocol.readIdleResponse();
+ synchronized (messageCacheLock) {
+ try {
+ idleEvents = new ArrayList();
+ try {
+ if (r == null || protocol == null ||
+ !protocol.processIdleResponse(r)) {
+ idleState = RUNNING;
+ messageCacheLock.notifyAll();
+ return null; // done
+ }
+ } catch (ProtocolException pex) {
+ idleState = RUNNING;
+ messageCacheLock.notifyAll();
+ throw pex; // also done
+ }
+ ret = idleEvents;
+ } catch (ConnectionException cex) {
+ // Oops, the store or folder died on us.
+ throwClosedException(cex);
+ } catch (ProtocolException pex) {
+ throw new MessagingException(pex.getMessage(), pex);
+ } finally {
+ idleEvents = null;
+ }
+ }
+ return ret;
+ }
+
/*
* If an IDLE command is in progress, abort it if necessary,
* and wait until it completes.
@@ -2952,7 +3224,11 @@
assert Thread.holdsLock(messageCacheLock);
while (idleState != RUNNING) {
if (idleState == IDLE) {
- protocol.idleAbort();
+ IdleManager im = idleManager;
+ if (im != null)
+ im.requestAbort(this);
+ else
+ protocol.idleAbort();
idleState = ABORTING;
}
try {
@@ -2962,6 +3238,23 @@
}
}
+ /*
+ * Send the DONE command that aborts the IDLE; used by IdleManager.
+ */
+ void idleAbort() {
+ idleManager = null; // don't need it anymore
+ if (protocol != null) // should always be true
+ protocol.idleAbort();
+ }
+
+ /**
+ * 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
@@ -3056,7 +3349,7 @@
total += count;
// avoid instantiating Message objects if no listeners.
- if (hasMessageCountListener) {
+ if (hasMessageCountListener || idleEvents != null) {
for (int i = 0; i < count; i++)
msgs[i] = messageCache.getMessage(++oldtotal);
@@ -3069,7 +3362,8 @@
int seqnum = ir.getNumber();
Message[] msgs = null;
- if (doExpungeNotification && hasMessageCountListener) {
+ if (doExpungeNotification &&
+ (hasMessageCountListener || idleEvents != null)) {
// save the Message object first; can't look it
// up after it's expunged
msgs = new Message[] { getMessageBySeqNumber(seqnum) };
@@ -3101,7 +3395,8 @@
if (m.getMessageNumber() > 0)
messageCache.expungeMessage(m.getMessageNumber());
}
- if (doExpungeNotification && hasMessageCountListener) {
+ if (doExpungeNotification &&
+ (hasMessageCountListener || idleEvents != null)) {
notifyMessageRemovedListeners(true, msgs);
}
} // else if (EARLIER), ignore