All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.mail.imap.IdleManager.3 Maven / Gradle / Ivy

There is a newer version: 2.0.1
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 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
 * 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://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/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 packager/legal/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.io.IOException;
import java.io.InterruptedIOException;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;
import java.util.logging.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;

import javax.mail.*;
import javax.mail.event.*;

import com.sun.mail.imap.protocol.IMAPProtocol;
import com.sun.mail.util.MailLogger;

/**
 * IdleManager uses the optional IMAP IDLE command
 * (RFC 2177)
 * to watch multiple folders for new messages.
 * IdleManager uses an Executor to execute tasks in separate threads.
 * An Executor is typically provided by an ExecutorService.
 * For example, for a Java SE application:
 * 
 *	ExecutorService es = Executors.newCachedThreadPool();
 *	final IdleManager idleManager = new IdleManager(session, es);
 * 
* For a Java EE 7 application: *
 *	{@literal @}Resource
 *	ManagedExecutorService es;
 *	final IdleManager idleManager = new IdleManager(session, es);
 * 
* To watch for new messages in a folder, open the folder, register a listener, * and ask the IdleManager to watch the folder: *
 *	Folder folder = store.getFolder("INBOX");
 *	folder.open(Folder.READ_WRITE);
 *	folder.addMessageCountListener(new MessageCountAdapter() {
 *	    public void messagesAdded(MessageCountEvent ev) {
 *		Folder folder = (Folder)ev.getSource();
 *		Message[] msgs = ev.getMessages();
 *		System.out.println("Folder: " + folder +
 *		    " got " + msgs.length + " new messages");
 *		// process new messages
 *		idleManager.watch(folder); // keep watching for new messages
 *	    }
 *	});
 *	idleManager.watch(folder);
 * 
* This delivers the events for each folder in a separate thread, NOT * using the Executor. To deliver all events in a single thread * using the Executor, register the listener with the IdleManager * (once), and then ask the IdleManager to watch each folder that you open: *
 *	// the following should be done once...
 *	idleManager.addMessageCountListener(new MessageCountAdapter() {
 *	    public void messagesAdded(MessageCountEvent ev) {
 *		Folder folder = (Folder)ev.getSource();
 *		Message[] msgs = ev.getMessages();
 *		System.out.println("Folder: " + folder +
 *		    " got " + msgs.length + " new messages");
 *		// process new messages
 *		idleManager.watch(folder); // keep watching for new messages
 *	    }
 *	});
 *
 *	// the following should be done for each folder to be watched...
 *	Folder folder = store.getFolder("INBOX");
 *	folder.open(Folder.READ_WRITE);
 *	idleManager.watch(folder);
 * 
* Note that, after processing new messages in your listener, or doing any * other operations on the folder in any other thread, you need to tell * the IdleManager to watch for more new messages. Unless, of course, you * close the folder. *

* The IdleManager is created with a Session, which it uses only to control * debug output. A single IdleManager instance can watch multiple Folders * from multiple Stores and multiple Sessions. *

* Due to limitations in the Java SE nio support, a * {@link java.nio.channels.SocketChannel SocketChannel} must be used instead * of a {@link java.net.Socket Socket} to connect to the server. However, * SocketChannels don't support all the features of Sockets, such as connecting * through a SOCKS proxy server. SocketChannels also don't support * simultaneous read and write, which means that the * {@link com.sun.mail.imap.IMAPFolder#idle idle} method can't be used if * SocketChannels are being used; use this IdleManager instead. * To enable support for SocketChannels instead of Sockets, set the * mail.imap.usesocketchannels property in the Session used to * access the IMAP Folder. (Or mail.imaps.usesocketchannels if * you're using the "imaps" protocol.) This will effect all connections in * that Session, but you can create another Session without this property set * if you need to use the features that are incompatible with SocketChannels. *

* NOTE: The IdleManager, and all APIs and properties related to it, should * be considered EXPERIMENTAL. They may be changed in the * future in ways that are incompatible with applications using the * current APIs. * * @since JavaMail 1.5.2 */ public class IdleManager { private Executor es; private Selector selector; private MailLogger logger; private volatile boolean die = false; private Queue toWatch = new ConcurrentLinkedQueue(); private Queue toAbort = new ConcurrentLinkedQueue(); private Queue events = new ConcurrentLinkedQueue(); private boolean eventDispatcherStarted = false; private Runnable eventDispatcher; private List messageCountListeners = Collections.synchronizedList(new ArrayList()); private List messageChangedListeners = Collections.synchronizedList(new ArrayList()); /** * Create an IdleManager. The Session is used only to configure * debugging output. The Executor is used to create the * "select" thread as well as the event delivery thread. * * @param session the Session containing configuration information * @param es the Executor used to create threads */ public IdleManager(Session session, Executor es) throws IOException { logger = new MailLogger(this.getClass(), "DEBUG IMAP", session); this.es = es; selector = Selector.open(); eventDispatcher = new Runnable() { public void run() { dispatchEvents(); } }; es.execute(new Runnable() { public void run() { select(); } }); } /** * Watch the Folder for new messages and other events using the IMAP IDLE * command. * * @param folder the folder to watch * @exception MessagingException for errors related to the folder * @exception IOException for SocketChannel errors */ public synchronized void watch(Folder folder) throws IOException, MessagingException { if (!(folder instanceof IMAPFolder)) throw new MessagingException("Can only watch IMAP folders"); IMAPFolder ifolder = (IMAPFolder)folder; SocketChannel sc = ifolder.getChannel(); if (sc == null) throw new MessagingException("Folder is not using SocketChannels"); logger.log(Level.FINEST, "IdleManager watching {0}", ifolder); ifolder.startIdle(this); toWatch.add(ifolder); selector.wakeup(); } /** * Add a listener for MessageCount events.

* * @param l the Listener for MessageCount events * @see javax.mail.event.MessageCountEvent */ public void addMessageCountListener(MessageCountListener l) { messageCountListeners.add(l); } /** * Remove a MessageCount listener.

* * @param l the listener * @see #addMessageCountListener */ public void removeMessageCountListener(MessageCountListener l) { messageCountListeners.remove(l); } /** * Add a listener for MessageChanged events.

* * @param l the Listener for MessageChanged events * @see javax.mail.event.MessageChangedEvent */ public void addMessageChangedListener(MessageChangedListener l) { messageChangedListeners.add(l); } /** * Remove a MessageChanged listener.

* * @param l the listener * @see #addMessageChangedListener */ public void removeMessageChangedListener(MessageChangedListener l) { messageChangedListeners.remove(l); } /** * Request that the specified folder abort an IDLE command. * We can't do the abort directly because the DONE message needs * to be sent through the (potentially) SSL socket, which means * we need to be in blocking I/O mode. We can only switch to * blocking I/O mode when not selecting, so wake up the selector, * which will process this request when it wakes up. */ synchronized void requestAbort(IMAPFolder folder) { toAbort.add(folder); selector.wakeup(); } /** * Run the {@link java.nio.channels.Selector#select select} loop * to poll each watched folder for events sent from the server. */ private void select() { die = false; try { while (!die) { watchAll(); logger.finest("IdleManager waiting..."); int ns = selector.select(); if (logger.isLoggable(Level.FINEST)) logger.log(Level.FINEST, "IdleManager selected {0} channels", ns); if (die || Thread.currentThread().isInterrupted()) break; /* * Process any selected folders. We cancel the * selection key for any selected folder, so if we * need to continue watching that folder it's added * to the toWatch list again. We can't actually * register that folder again until the previous * selectionkey is cancelled, so we call selectNow() * just for the side effect of cancelling the selection * keys. But if selectNow() selects something, we * process it before adding folders from the toWatch * queue. And so on until there is nothing to do, at * which point it's safe to register folders from the * toWatch queue. */ while (processKeys() && selector.selectNow() > 0) ; } } catch (InterruptedIOException ex) { logger.log(Level.FINE, "IdleManager interrupted", ex); } catch (IOException ex) { logger.log(Level.FINE, "IdleManager got exception", ex); } finally { try { unwatchAll(); selector.close(); } catch (IOException ex2) { // nothing to do... } logger.fine("IdleManager exiting"); } } /** * Register all of the folders in the queue with the selector, * switching them to nonblocking I/O mode first. */ private void watchAll() { /* * Pull each of the folders from the toWatch queue * and register it. */ IMAPFolder folder; while ((folder = toWatch.poll()) != null) { logger.log(Level.FINEST, "IdleManager adding {0} to selector", folder); SocketChannel sc = folder.getChannel(); if (sc == null) continue; try { // has to be non-blocking to select sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ, folder); } catch (IOException ex) { // oh well, nothing to do logger.log(Level.FINEST, "IdleManager can't register folder", ex); } } } /** * Process the selected keys, returning true if any folders have * been added to the watch list. */ private boolean processKeys() throws IOException { boolean more = false; /* * First, process any folders that we need to abort. */ IMAPFolder folder; while ((folder = toAbort.poll()) != null) { logger.log(Level.FINE, "IdleManager aborting IDLE for folder: {0}", folder); SocketChannel sc = folder.getChannel(); if (sc == null) continue; SelectionKey sk = sc.keyFor(selector); // have to cancel so we can switch back to blocking I/O mode if (sk != null) sk.cancel(); // switch back to blocking to allow normal I/O sc.configureBlocking(true); folder.idleAbort(); // send the DONE message // watch for OK response to DONE toWatch.add(folder); more = true; } /* * Now, process any channels with data to read. */ Set selectedKeys = selector.selectedKeys(); for (SelectionKey sk : selectedKeys) { selectedKeys.remove(sk); // only process each key once // have to cancel so we can switch back to blocking I/O mode sk.cancel(); folder = (IMAPFolder)sk.attachment(); logger.log(Level.FINE, "IdleManager selected folder: {0}", folder); SelectableChannel sc = sk.channel(); // switch back to blocking to allow normal I/O sc.configureBlocking(true); try { List evs; if ((evs = folder.handleIdleEvents()) != null) { logger.log(Level.FINEST, "IdleManager got {0} events", events.size()); // more to do with this folder, select on it again // XXX - what if we also added it above? toWatch.add(folder); more = true; events.addAll(evs); startEventDispatcher(); } else { // done watching this folder, logger.log(Level.FINE, "IdleManager done watching folder {0}", folder); } } catch (MessagingException ex) { // something went wrong, stop watching this folder logger.log(Level.FINE, "IdleManager got exception for folder: " + folder, ex); } } return more; } /** * Stop watching all folders. Cancel any selection keys and, * most importantly, switch the channel back to blocking mode. */ private void unwatchAll() { Set keys = selector.keys(); for (SelectionKey sk : keys) { // have to cancel so we can switch back to blocking I/O mode sk.cancel(); IMAPFolder folder = (IMAPFolder)sk.attachment(); logger.log(Level.FINE, "IdleManager no longer watching folder: {0}", folder); SelectableChannel sc = sk.channel(); // switch back to blocking to allow normal I/O try { sc.configureBlocking(true); } catch (IOException ex) { // ignore it, channel might be closed } } } private synchronized void startEventDispatcher() { if (!eventDispatcherStarted) { es.execute(eventDispatcher); eventDispatcherStarted = true; } } private synchronized void eventDispatcherStopped() { eventDispatcherStarted = false; } /** * Dispatch the events in the event queue to the appropriate listeners * based on the type of the event. */ private void dispatchEvents() { for (;;) { MailEvent e; synchronized (this) { if ((e = events.poll()) == null) { // no more events in the queue eventDispatcherStarted = false; return; } } logger.log(Level.FINEST, "IdleManager deliver event: {0}", e); if (e instanceof MessageCountEvent) { for (MessageCountListener l : messageCountListeners) { try { e.dispatch(l); } catch (Throwable t) { logger.log(Level.FINEST, "IdleManager listener failed!", t); // ignore anything thrown by the listener } } } else if (e instanceof MessageChangedEvent) { for (MessageChangedListener l : messageChangedListeners) { try { e.dispatch(l); } catch (Throwable t) { logger.log(Level.FINEST, "IdleManager listener failed!", t); // ignore anything thrown by the listener } } } else { logger.finest("IdleManager unknown event type!"); // can't happen, I hope } } } /** * Stop the IdleManager. The IdleManager can not be restarted. */ public synchronized void stop() { die = true; logger.finest("IdleManager stopping"); selector.wakeup(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy