From aa6d4a4ac75ff418148574e06f795b3c39913a49 Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Mon, 19 Nov 2012 18:36:54 -0500 Subject: [PATCH] INT-2821 Find Existing Mails if No RECENT Support Previously, if an IMAP server supports IDLE, but not RECENT, no existing messages were retrieved until a new message arrived. INT-2821 Polishing - Fix Race Condition Use searchForMessages() when server doesn't support RECENT. Simply skipping the first idle() doesn't work because receive() closes the folder, there was a race condition where a new message could arrive between the previous receive() and folder.open() in waitForMessages(). Updated mock test so that first call to waitForMessages() finds messages and does not idle(), and subsequent calls does not find messages and goes to idle(). Also tested in the debugger against gmail which (at the time of writing) does not support RECENT. --- .../integration/mail/ImapMailReceiver.java | 5 + .../mail/ImapMailReceiverTests.java | 158 ++++++++++++++++++ 2 files changed, 163 insertions(+) diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/ImapMailReceiver.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/ImapMailReceiver.java index 7518129a77..21295bb9b8 100755 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/ImapMailReceiver.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/ImapMailReceiver.java @@ -110,6 +110,11 @@ public class ImapMailReceiver extends AbstractMailReceiver { if (imapFolder.hasNewMessages()) { return; } + else if (!folder.getPermanentFlags().contains(Flags.Flag.RECENT)) { + if (searchForNewMessages().length > 0) { + return; + } + } imapFolder.addMessageCountListener(this.messageCountListener); try { imapFolder.idle(); diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java index 91854aa0c9..704e73fcf8 100644 --- a/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailReceiverTests.java @@ -17,7 +17,9 @@ package org.springframework.integration.mail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -29,6 +31,8 @@ import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Field; import java.util.Properties; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -409,6 +413,160 @@ public class ImapMailReceiverTests { assertEquals("Failed", ((Exception) replMessage.getPayload()).getCause().getMessage()); } + @Test + public void testNoInitialIdleDelayWhenRecentNotSupported() throws Exception{ + ApplicationContext context = + new ClassPathXmlApplicationContext("ImapIdleChannelAdapterParserTests-context.xml", ImapIdleChannelAdapterParserTests.class); + ImapIdleChannelAdapter adapter = context.getBean("simpleAdapter", ImapIdleChannelAdapter.class); + + QueueChannel channel = new QueueChannel(); + adapter.setOutputChannel(channel); + + ImapMailReceiver receiver = new ImapMailReceiver("imap:foo"); + receiver = spy(receiver); + receiver.afterPropertiesSet(); + + final IMAPFolder folder = mock(IMAPFolder.class); + when(folder.getPermanentFlags()).thenReturn(new Flags(Flags.Flag.USER)); + when(folder.isOpen()).thenReturn(false).thenReturn(true); + when(folder.exists()).thenReturn(true); + + DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter); + adapterAccessor.setPropertyValue("mailReceiver", receiver); + + Field storeField = AbstractMailReceiver.class.getDeclaredField("store"); + storeField.setAccessible(true); + Store store = mock(Store.class); + when(store.isConnected()).thenReturn(true); + when(store.getFolder(Mockito.any(URLName.class))).thenReturn(folder); + storeField.set(receiver, store); + + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) throws Throwable { + return folder; + } + }).when(receiver).getFolder(); + + MimeMessage mailMessage = mock(MimeMessage.class); + Flags flags = mock(Flags.class); + when(mailMessage.getFlags()).thenReturn(flags); + final Message[] messages = new Message[]{mailMessage}; + + final AtomicInteger shouldFindMessagesCounter = new AtomicInteger(2); + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) throws Throwable { + /* + * Return the message from first invocation of waitForMessages() + * and in receive(); then return false in the next call to + * waitForMessages() so we enter idle(); counter will be reset + * to 1 in the mocked idle(). + */ + if (shouldFindMessagesCounter.decrementAndGet() >= 0) { + return messages; + } + else { + return new Message[0]; + } + } + }).when(receiver).searchForNewMessages(); + + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) throws Throwable { + return null; + } + }).when(receiver).fetchMessages(messages); + + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) throws Throwable { + Thread.sleep(5000); + shouldFindMessagesCounter.set(1); + return null; + } + }).when(folder).idle(); + + adapter.start(); + + /* + * Idle takes 5 seconds; if all is well, we should receive the first message + * before then. + */ + assertNotNull(channel.receive(3000)); + // We should not receive any more until the next idle elapses + assertNull(channel.receive(3000)); + assertNotNull(channel.receive(6000)); + } + + @Test + public void testInitialIdleDelayWhenRecentIsSupported() throws Exception{ + ApplicationContext context = + new ClassPathXmlApplicationContext("ImapIdleChannelAdapterParserTests-context.xml", ImapIdleChannelAdapterParserTests.class); + ImapIdleChannelAdapter adapter = context.getBean("simpleAdapter", ImapIdleChannelAdapter.class); + + QueueChannel channel = new QueueChannel(); + adapter.setOutputChannel(channel); + + ImapMailReceiver receiver = new ImapMailReceiver("imap:foo"); + receiver = spy(receiver); + receiver.afterPropertiesSet(); + + final IMAPFolder folder = mock(IMAPFolder.class); + when(folder.getPermanentFlags()).thenReturn(new Flags(Flags.Flag.RECENT)); + when(folder.isOpen()).thenReturn(false).thenReturn(true); + when(folder.exists()).thenReturn(true); + + DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter); + adapterAccessor.setPropertyValue("mailReceiver", receiver); + + Field storeField = AbstractMailReceiver.class.getDeclaredField("store"); + storeField.setAccessible(true); + Store store = mock(Store.class); + when(store.isConnected()).thenReturn(true); + when(store.getFolder(Mockito.any(URLName.class))).thenReturn(folder); + storeField.set(receiver, store); + + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) throws Throwable { + return folder; + } + }).when(receiver).getFolder(); + + MimeMessage mailMessage = mock(MimeMessage.class); + Flags flags = mock(Flags.class); + when(mailMessage.getFlags()).thenReturn(flags); + final Message[] messages = new Message[]{mailMessage}; + + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) throws Throwable { + return messages; + } + }).when(receiver).searchForNewMessages(); + + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) throws Throwable { + return null; + } + }).when(receiver).fetchMessages(messages); + + final CountDownLatch idles = new CountDownLatch(2); + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) throws Throwable { + idles.countDown(); + Thread.sleep(5000); + return null; + } + }).when(folder).idle(); + + adapter.start(); + + /* + * Idle takes 5 seconds; since this server supports RECENT, we should + * not receive any early messages. + */ + assertNull(channel.receive(3000)); + assertNotNull(channel.receive(5000)); + assertTrue(idles.await(5, TimeUnit.SECONDS)); + } + @Test // see INT-1801 public void testImapLifecycleForRaceCondition() throws Exception{