0

I am trying to implement a javax.mail.event.MessageCountListener in Tomcat. When I start the application the contextInitialized method seems to run and the mailbox is read. However, I see the log message "Idling" only once. I would expect that it would idle constantly and invoke the AnalyzerService() when an email is received or deleted.

Update: Found that the idle() method is not returning. It runs untill com.sun.mail.iap.ResponseInputStream.readResponse(ByteArray ba) method where it runs into a while loop where it never gets out.

Am I misusing the idle() method for something I should not do? Is this a bug in com.sun.mail.iap package?

The AnalyzerContextListener.java:

import com.sun.mail.imap.IMAPStore;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.mail.Folder;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.event.MessageCountListener;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class AnalyzerContextListener implements ServletContextListener {

    private ExecutorService executorService;
    private final String username = "myemail@gmail.com";
    private final String password = "mypassword";
    private final String mailhost = "imap.gmail.com";
    private final String foldername = "INBOX";

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        final ServletContext servletContext = sce.getServletContext();
        executorService = Executors.newFixedThreadPool(3);
        Session session = Session.getInstance(new Properties());
        try {
            final IMAPStore store = (IMAPStore) session.getStore("imaps");
            store.connect(mailhost, username, password);
            final Folder folder = store.getFolder(foldername);
            if (folder == null) {
                servletContext.log("Folder in mailbox bestaat niet.");
                return;
            }
            folder.open(Folder.READ_ONLY);
            MessageCountListener countListener = new AnalyzerService();
            folder.addMessageCountListener(countListener);

            Runnable runnable = new Runnable() {

                @Override
                public void run() {
                    while (true) {
                        try {
                            servletContext.log("Aantal berichten in folder: " + folder.getMessageCount());
                            servletContext.log("Idling");
                            store.idle();
                        } catch (MessagingException ex) {
                            servletContext.log(ex.getMessage());
                            return;
                        }
                    }
                }
            };
            executorService.execute(runnable);
            servletContext.log("Executorservice gestart");
        } catch (MessagingException ex) {
            servletContext.log(ex.getMessage());
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        sce.getServletContext().log("Context wordt vernietigd");
        executorService.shutdown();
        sce.getServletContext().log("Executorservice gestopt");
    }
}

The AnalyzerService.java:

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.event.MessageCountEvent;
import javax.mail.event.MessageCountListener;

class AnalyzerService implements MessageCountListener {

    public AnalyzerService() {
    }

    @Override
    public void messagesAdded(MessageCountEvent event) {

        Message[] addedMessages = event.getMessages();
        for (Message message : addedMessages) {
            try {
                System.out.println(message.getSubject());
            } catch (MessagingException ex) {
                System.out.println(ex.getMessage());
            }
        }
    }

    @Override
    public void messagesRemoved(MessageCountEvent event) {

        Message[] removedMessages = event.getMessages();
        for (Message message : removedMessages) {
            try {
                System.out.println(message.getSubject());
            } catch (MessagingException ex) {
                System.out.println(ex.getMessage());
            }
        }

    }

}
Martijn Burger
  • 7,315
  • 8
  • 54
  • 94

1 Answers1

2
while (true) {
    try {
        servletContext.log("Aantal berichten in folder: " + folder.getMessageCount());
        servletContext.log("Idling");
        store.idle();
    } catch (MessagingException ex) {
        servletContext.log(ex.getMessage());
        return;
    }
}

has exactly 2 3 possibilities to end earlier than never run only once.

  • The loop actually ends either:

    1. Through the explicit return in case of a MessagingException. Look at your logs, there either a message or something strange like "null". Consider using a proper stacktrace log (.log(String message, Throwable throwable)) since Exception#getMessage() is often empty or not telling you much.

    2. Through any unchecked exception. You should notice that in some log though since uncaught exceptions via executorService.execute should invoke the nearest uncaught exeption handler which is generally bad. See Choose between ExecutorService's submit and ExecutorService's execute

  • The loop stops executing after it logs "Idling"

    1. store.idle() never returns. (every other line of code could do that theoretically as well, e.g. the folder.getMessageCount() call in a 2nd iteration but that's very unlikely)

Regarding No 3 - the documentation

Use the IMAP IDLE command (see RFC 2177), if supported by the server, to enter idle mode so that the server can send unsolicited notifications without the need for the client to constantly poll the server. Use a ConnectionListener to be notified of events. When another thread (e.g., the listener thread) needs to issue an IMAP comand for this Store, the idle mode will be terminated and this method will return. Typically the caller will invoke this method in a loop.

If the mail.imap.enableimapevents property is set, notifications received while the IDLE command is active will be delivered to ConnectionListeners as events with a type of IMAPStore.RESPONSE. The event's message will be the raw IMAP response string. Note that most IMAP servers will not deliver any events when using the IDLE command on a connection with no mailbox selected (i.e., this method). In most cases you'll want to use the idle method on IMAPFolder.

That sounds like this method is not designed to return any time soon. In your case never since you don't issue any commands towards the server after you enter idle. Besides that

  • folder.idle() could be what you should actually do
  • I guess the documentation is wrong, however ConnectionListener and MessageCountListener are two different things.
Community
  • 1
  • 1
zapl
  • 63,179
  • 10
  • 123
  • 154
  • 2
    The other possibility is that `idle()` is never returning. – user207421 Oct 29 '14 at 00:41
  • @EJP +1 I didn't even consider that the loop could get stuck. – zapl Oct 29 '14 at 01:09
  • Yes! That's the one. Thank you @EJP Idle is not returning when it runs for the second time. I guess there's something with final variables and the inner runnable class. – Martijn Burger Oct 29 '14 at 08:50
  • @MartijnBurger you are "misusing" it as far as I can tell. The method is not intended to return unless you have a request for the server (which temporarily ends idle mode), like fetching newly arrived mails. Waiting to get notified is no request. You're still supposed to call that method in a loop because after you've fetched those mails you usually want to continue with idle mode. If your listener isn't getting anything, try if `folder.idle()` changes something. – zapl Oct 29 '14 at 12:18
  • @MartijnBurger looks like you could add a line of code to fetch some message properties to your listener like done here http://grepcode.com/file_/repo1.maven.org/maven2/org.springframework.ws/spring-ws-support/2.2.0.RELEASE/org/springframework/ws/transport/mail/monitor/ImapIdleMonitoringStrategy.java/?v=source (otherwise pointless read of `message.getLineCount()`) to make your code behave the way you expected it to be. – zapl Oct 29 '14 at 12:56
  • Correction: You do that already via `System.out.println(message.getSubject())` as far as I can tell. So it's basically just the `folder.idle()` part. You won't get notified for changes unless you "subscribe" to events for a certain folder. – zapl Oct 29 '14 at 13:03
  • I thought I was 'subscribing' a MessageCountListener with the following code: MessageCountListener countListener = new AnalyzerService(); folder.addMessageCountListener(countListener); – Martijn Burger Oct 29 '14 at 13:07
  • @MartijnBurger http://stackoverflow.com/a/23662443/995891 - I guess adding a listener is just the part that defines where notifications go if you receive them, the part that requests them is .idle(). – zapl Oct 29 '14 at 13:47
  • 1
    Solved! @zapl thanks for the link to the Spring FW code. Helped me solve it. I needed IMAPFolder instead of folder: IMAPFolder folder = (IMAPFolder) store.getFolder(foldername); – Martijn Burger Oct 29 '14 at 14:11