/* * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.integration.mail; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; 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.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import java.io.IOException; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import javax.mail.Flags; import javax.mail.Flags.Flag; import javax.mail.Folder; import javax.mail.FolderClosedException; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.Store; import javax.mail.URLName; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import javax.mail.search.AndTerm; import javax.mail.search.FlagTerm; import javax.mail.search.FromTerm; import javax.mail.search.SearchTerm; import org.apache.commons.logging.Log; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.mockito.Mockito; import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.ClassPathResource; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.integration.history.MessageHistory; import org.springframework.integration.mail.ImapIdleChannelAdapter.ImapIdleExceptionEvent; import org.springframework.integration.mail.config.ImapIdleChannelAdapterParserTests; import org.springframework.integration.mail.support.DefaultMailHeaderMapper; import org.springframework.integration.test.mail.TestMailServer; import org.springframework.integration.test.mail.TestMailServer.ImapServer; import org.springframework.integration.test.support.LongRunningIntegrationTest; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.PollableChannel; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import com.sun.mail.imap.IMAPFolder; /** * @author Oleg Zhurakousky * @author Gary Russell * @author Artem Bilan */ public class ImapMailReceiverTests { @Rule public final LongRunningIntegrationTest longRunningIntegrationTest = new LongRunningIntegrationTest(); private final AtomicInteger failed = new AtomicInteger(0); private final static ImapServer imapIdleServer = TestMailServer.imap(0); @BeforeClass public static void setup() throws InterruptedException { int n = 0; while (n++ < 100 && (!imapIdleServer.isListening())) { Thread.sleep(100); } assertTrue(n < 100); } @AfterClass public static void tearDown() { imapIdleServer.stop(); } @Test public void testIdleWithServerCustomSearch() throws Exception { ImapMailReceiver receiver = new ImapMailReceiver("imap://user:pw@localhost:" + imapIdleServer.getPort() + "/INBOX"); receiver.setSearchTermStrategy((supportedFlags, folder) -> { try { FromTerm fromTerm = new FromTerm(new InternetAddress("bar@baz")); return new AndTerm(fromTerm, new FlagTerm(new Flags(Flag.SEEN), false)); } catch (AddressException e) { throw new RuntimeException(e); } }); testIdleWithServerGuts(receiver, false); } @Test public void testIdleWithServerDefaultSearch() throws Exception { ImapMailReceiver receiver = new ImapMailReceiver("imap://user:pw@localhost:" + imapIdleServer.getPort() + "/INBOX"); testIdleWithServerGuts(receiver, false); assertTrue(imapIdleServer.assertReceived("searchWithUserFlag")); } @Test public void testIdleWithMessageMapping() throws Exception { ImapMailReceiver receiver = new ImapMailReceiver("imap://user:pw@localhost:" + imapIdleServer.getPort() + "/INBOX"); receiver.setHeaderMapper(new DefaultMailHeaderMapper()); testIdleWithServerGuts(receiver, true); } @Test public void testIdleWithServerDefaultSearchSimple() throws Exception { ImapMailReceiver receiver = new ImapMailReceiver("imap://user:pw@localhost:" + imapIdleServer.getPort() + "/INBOX"); receiver.setSimpleContent(true); testIdleWithServerGuts(receiver, false, true); assertTrue(imapIdleServer.assertReceived("searchWithUserFlag")); } @Test public void testIdleWithMessageMappingSimple() throws Exception { ImapMailReceiver receiver = new ImapMailReceiver("imap://user:pw@localhost:" + imapIdleServer.getPort() + "/INBOX"); receiver.setSimpleContent(true); receiver.setHeaderMapper(new DefaultMailHeaderMapper()); testIdleWithServerGuts(receiver, true, true); } public void testIdleWithServerGuts(ImapMailReceiver receiver, boolean mapped) throws Exception { testIdleWithServerGuts(receiver, mapped, false); } public void testIdleWithServerGuts(ImapMailReceiver receiver, boolean mapped, boolean simple) throws Exception { imapIdleServer.resetServer(); Properties mailProps = new Properties(); mailProps.put("mail.debug", "true"); mailProps.put("mail.imap.connectionpool.debug", "true"); receiver.setJavaMailProperties(mailProps); receiver.setMaxFetchSize(1); receiver.setShouldDeleteMessages(false); receiver.setShouldMarkMessagesAsRead(true); receiver.setCancelIdleInterval(8); ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); setUpScheduler(receiver, taskScheduler); receiver.setUserFlag("testSIUserFlag"); receiver.afterPropertiesSet(); Log logger = spy(TestUtils.getPropertyValue(receiver, "logger", Log.class)); new DirectFieldAccessor(receiver).setPropertyValue("logger", logger); ImapIdleChannelAdapter adapter = new ImapIdleChannelAdapter(receiver); QueueChannel channel = new QueueChannel(); adapter.setOutputChannel(channel); adapter.setTaskScheduler(taskScheduler); adapter.start(); if (!mapped) { @SuppressWarnings("unchecked") org.springframework.messaging.Message<MimeMessage> received = (org.springframework.messaging.Message<MimeMessage>) channel.receive(10000); assertNotNull(received); assertNotNull(received.getPayload().getReceivedDate()); assertTrue(received.getPayload().getLineCount() > -1); if (simple) { assertThat(received.getPayload().getContent(), equalTo(TestMailServer.MailServer.MailHandler.BODY + "\r\n")); } else { assertThat(received.getPayload().getContent(), equalTo(TestMailServer.MailServer.MailHandler.MESSAGE + "\r\n")); } } else { org.springframework.messaging.Message<?> received = channel.receive(10000); assertNotNull(received); MessageHeaders headers = received.getHeaders(); assertNotNull(headers.get(MailHeaders.RAW_HEADERS)); assertThat(headers.get(MailHeaders.CONTENT_TYPE), equalTo("TEXT/PLAIN; charset=ISO-8859-1")); assertThat(headers.get(MessageHeaders.CONTENT_TYPE), equalTo("TEXT/PLAIN; charset=ISO-8859-1")); assertThat(headers.get(MailHeaders.FROM), equalTo("Bar <bar@baz>")); assertThat((headers.get(MailHeaders.TO, String[].class))[0], equalTo("Foo <foo@bar>")); assertThat(Arrays.toString(headers.get(MailHeaders.CC, String[].class)), equalTo("[a@b, c@d]")); assertThat(Arrays.toString(headers.get(MailHeaders.BCC, String[].class)), equalTo("[e@f, g@h]")); assertThat(headers.get(MailHeaders.SUBJECT), equalTo("Test Email")); if (simple) { assertThat(received.getPayload(), equalTo(TestMailServer.MailServer.MailHandler.BODY + "\r\n")); } else { assertThat(received.getPayload(), equalTo(TestMailServer.MailServer.MailHandler.MESSAGE + "\r\n")); } } assertNotNull(channel.receive(10000)); // new message after idle assertNull(channel.receive(10000)); // no new message after second and third idle verify(logger).debug("Canceling IDLE"); taskScheduler.shutdown(); assertTrue(imapIdleServer.assertReceived("storeUserFlag")); } @Test public void receiveAndMarkAsReadDontDelete() throws Exception { AbstractMailReceiver receiver = new ImapMailReceiver(); Message msg1 = mock(MimeMessage.class); Message msg2 = mock(MimeMessage.class); receiver = receiveAndMarkAsReadDontDeleteGuts(receiver, msg1, msg2); verify(msg1, times(1)).setFlag(Flag.SEEN, true); verify(msg2, times(1)).setFlag(Flag.SEEN, true); verify(receiver, times(0)).deleteMessages((Message[]) Mockito.any()); } private AbstractMailReceiver receiveAndMarkAsReadDontDeleteGuts(AbstractMailReceiver receiver, Message msg1, Message msg2) throws NoSuchFieldException, IllegalAccessException, MessagingException { ((ImapMailReceiver) receiver).setShouldMarkMessagesAsRead(true); receiver = spy(receiver); receiver.setBeanFactory(mock(BeanFactory.class)); receiver.afterPropertiesSet(); Field folderField = AbstractMailReceiver.class.getDeclaredField("folder"); folderField.setAccessible(true); Folder folder = mock(Folder.class); given(folder.getPermanentFlags()).willReturn(new Flags(Flags.Flag.USER)); folderField.set(receiver, folder); final Message[] messages = new Message[] { msg1, msg2 }; willAnswer(invocation -> { DirectFieldAccessor accessor = new DirectFieldAccessor(invocation.getMock()); int folderOpenMode = (Integer) accessor.getPropertyValue("folderOpenMode"); if (folderOpenMode != Folder.READ_WRITE) { throw new IllegalArgumentException("Folder had to be open in READ_WRITE mode"); } return null; }).given(receiver).openFolder(); willAnswer(invocation -> messages).given(receiver).searchForNewMessages(); willAnswer(invocation -> null).given(receiver).fetchMessages(messages); receiver.receive(); return receiver; } @Test // INT-2991 Flag.SEEN was set twice when a filter is used public void receiveAndMarkAsReadDontDeletePassingFilter() throws Exception { AbstractMailReceiver receiver = new ImapMailReceiver(); Message msg1 = mock(MimeMessage.class); Message msg2 = mock(MimeMessage.class); Expression selectorExpression = new SpelExpressionParser().parseExpression("true"); receiver.setSelectorExpression(selectorExpression); receiver = receiveAndMarkAsReadDontDeleteGuts(receiver, msg1, msg2); verify(msg1, times(1)).setFlag(Flag.SEEN, true); verify(msg2, times(1)).setFlag(Flag.SEEN, true); verify(receiver, times(0)).deleteMessages((Message[]) Mockito.any()); } @Test // INT-2991 filtered messages were marked SEEN public void receiveAndMarkAsReadDontDeleteFiltered() throws Exception { AbstractMailReceiver receiver = new ImapMailReceiver(); Message msg1 = mock(MimeMessage.class); Message msg2 = mock(MimeMessage.class); given(msg2.getSubject()).willReturn("foo"); // should not be marked seen Expression selectorExpression = new SpelExpressionParser() .parseExpression("subject == null OR !subject.equals('foo')"); receiver.setSelectorExpression(selectorExpression); receiver = receiveAndMarkAsReadDontDeleteGuts(receiver, msg1, msg2); verify(msg1, times(1)).setFlag(Flag.SEEN, true); verify(msg2, never()).setFlag(Flag.SEEN, true); verify(receiver, times(0)).deleteMessages((Message[]) Mockito.any()); } @Test public void receiveMarkAsReadAndDelete() throws Exception { AbstractMailReceiver receiver = new ImapMailReceiver(); ((ImapMailReceiver) receiver).setShouldMarkMessagesAsRead(true); receiver.setShouldDeleteMessages(true); receiver = spy(receiver); receiver.setBeanFactory(mock(BeanFactory.class)); receiver.afterPropertiesSet(); Field folderField = AbstractMailReceiver.class.getDeclaredField("folder"); folderField.setAccessible(true); Folder folder = mock(Folder.class); given(folder.getPermanentFlags()).willReturn(new Flags(Flags.Flag.USER)); folderField.set(receiver, folder); Message msg1 = mock(MimeMessage.class); Message msg2 = mock(MimeMessage.class); final Message[] messages = new Message[] { msg1, msg2 }; willAnswer(invocation -> { DirectFieldAccessor accessor = new DirectFieldAccessor(invocation.getMock()); int folderOpenMode = (Integer) accessor.getPropertyValue("folderOpenMode"); if (folderOpenMode != Folder.READ_WRITE) { throw new IllegalArgumentException("Folder had to be open in READ_WRITE mode"); } return null; }).given(receiver).openFolder(); willAnswer(invocation -> messages).given(receiver).searchForNewMessages(); willAnswer(invocation -> null).given(receiver).fetchMessages(messages); receiver.receive(); verify(msg1, times(1)).setFlag(Flag.SEEN, true); verify(msg2, times(1)).setFlag(Flag.SEEN, true); verify(receiver, times(1)).deleteMessages((Message[]) Mockito.any()); } @Test public void receiveAndDontMarkAsRead() throws Exception { AbstractMailReceiver receiver = new ImapMailReceiver(); ((ImapMailReceiver) receiver).setShouldMarkMessagesAsRead(false); receiver = spy(receiver); receiver.setBeanFactory(mock(BeanFactory.class)); receiver.afterPropertiesSet(); Field folderField = AbstractMailReceiver.class.getDeclaredField("folder"); folderField.setAccessible(true); Folder folder = mock(Folder.class); given(folder.getPermanentFlags()).willReturn(new Flags(Flags.Flag.USER)); folderField.set(receiver, folder); Message msg1 = mock(MimeMessage.class); Message msg2 = mock(MimeMessage.class); final Message[] messages = new Message[] { msg1, msg2 }; willAnswer(invocation -> null).given(receiver).openFolder(); willAnswer(invocation -> messages).given(receiver).searchForNewMessages(); willAnswer(invocation -> null).given(receiver).fetchMessages(messages); receiver.afterPropertiesSet(); receiver.receive(); verify(msg1, times(0)).setFlag(Flag.SEEN, true); verify(msg2, times(0)).setFlag(Flag.SEEN, true); } @Test public void receiveAndDontMarkAsReadButDelete() throws Exception { AbstractMailReceiver receiver = new ImapMailReceiver(); receiver.setShouldDeleteMessages(true); ((ImapMailReceiver) receiver).setShouldMarkMessagesAsRead(false); receiver = spy(receiver); receiver.setBeanFactory(mock(BeanFactory.class)); receiver.afterPropertiesSet(); Field folderField = AbstractMailReceiver.class.getDeclaredField("folder"); folderField.setAccessible(true); Folder folder = mock(Folder.class); given(folder.getPermanentFlags()).willReturn(new Flags(Flags.Flag.USER)); folderField.set(receiver, folder); Message msg1 = mock(MimeMessage.class); Message msg2 = mock(MimeMessage.class); final Message[] messages = new Message[] { msg1, msg2 }; willAnswer(invocation -> { DirectFieldAccessor accessor = new DirectFieldAccessor(invocation.getMock()); int folderOpenMode = (Integer) accessor.getPropertyValue("folderOpenMode"); if (folderOpenMode != Folder.READ_WRITE) { throw new IllegalArgumentException("Folder had to be open in READ_WRITE mode"); } return null; }).given(receiver).openFolder(); willAnswer(invocation -> messages).given(receiver).searchForNewMessages(); willAnswer(invocation -> null).given(receiver).fetchMessages(messages); receiver.afterPropertiesSet(); receiver.receive(); verify(msg1, times(0)).setFlag(Flag.SEEN, true); verify(msg2, times(0)).setFlag(Flag.SEEN, true); verify(msg1, times(1)).setFlag(Flag.DELETED, true); verify(msg2, times(1)).setFlag(Flag.DELETED, true); } @Test public void receiveAndIgnoreMarkAsReadDontDelete() throws Exception { AbstractMailReceiver receiver = new ImapMailReceiver(); receiver = spy(receiver); receiver.setBeanFactory(mock(BeanFactory.class)); receiver.afterPropertiesSet(); Field folderField = AbstractMailReceiver.class.getDeclaredField("folder"); folderField.setAccessible(true); Folder folder = mock(Folder.class); given(folder.getPermanentFlags()).willReturn(new Flags(Flags.Flag.USER)); folderField.set(receiver, folder); Message msg1 = mock(MimeMessage.class); Message msg2 = mock(MimeMessage.class); final Message[] messages = new Message[] { msg1, msg2 }; willAnswer(invocation -> { DirectFieldAccessor accessor = new DirectFieldAccessor(invocation.getMock()); int folderOpenMode = (Integer) accessor.getPropertyValue("folderOpenMode"); if (folderOpenMode != Folder.READ_WRITE) { throw new IllegalArgumentException("Folder had to be open in READ_WRITE mode"); } return null; }).given(receiver).openFolder(); willAnswer(invocation -> messages).given(receiver).searchForNewMessages(); willAnswer(invocation -> null).given(receiver).fetchMessages(messages); receiver.receive(); verify(msg1, times(1)).setFlag(Flag.SEEN, true); verify(msg2, times(1)).setFlag(Flag.SEEN, true); verify(receiver, times(0)).deleteMessages((Message[]) Mockito.any()); } @Test @Ignore public void testMessageHistory() throws Exception { ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("ImapIdleChannelAdapterParserTests-context.xml", ImapIdleChannelAdapterParserTests.class); ImapIdleChannelAdapter adapter = context.getBean("simpleAdapter", ImapIdleChannelAdapter.class); AbstractMailReceiver receiver = new ImapMailReceiver(); receiver = spy(receiver); receiver.setBeanFactory(mock(BeanFactory.class)); receiver.afterPropertiesSet(); DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter); adapterAccessor.setPropertyValue("mailReceiver", receiver); MimeMessage mailMessage = mock(MimeMessage.class); Flags flags = mock(Flags.class); given(mailMessage.getFlags()).willReturn(flags); final Message[] messages = new Message[] { mailMessage }; willAnswer(invocation -> { DirectFieldAccessor accessor = new DirectFieldAccessor((invocation.getMock())); IMAPFolder folder = mock(IMAPFolder.class); accessor.setPropertyValue("folder", folder); given(folder.hasNewMessages()).willReturn(true); return null; }).given(receiver).openFolder(); willAnswer(invocation -> messages).given(receiver).searchForNewMessages(); willAnswer(invocation -> null).given(receiver).fetchMessages(messages); PollableChannel channel = context.getBean("channel", PollableChannel.class); adapter.start(); org.springframework.messaging.Message<?> replMessage = channel.receive(10000); MessageHistory history = MessageHistory.read(replMessage); assertNotNull(history); Properties componentHistoryRecord = TestUtils.locateComponentInHistory(history, "simpleAdapter", 0); assertNotNull(componentHistoryRecord); assertEquals("mail:imap-idle-channel-adapter", componentHistoryRecord.get("type")); adapter.stop(); context.close(); } @Test public void testIdleChannelAdapterException() throws Exception { ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("ImapIdleChannelAdapterParserTests-context.xml", ImapIdleChannelAdapterParserTests.class); ImapIdleChannelAdapter adapter = context.getBean("simpleAdapter", ImapIdleChannelAdapter.class); //ImapMailReceiver receiver = (ImapMailReceiver) TestUtils.getPropertyValue(adapter, "mailReceiver"); DirectChannel channel = new DirectChannel(); channel.subscribe(new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(org.springframework.messaging.Message<?> requestMessage) { throw new RuntimeException("Failed"); } }); adapter.setOutputChannel(channel); QueueChannel errorChannel = new QueueChannel(); adapter.setErrorChannel(errorChannel); AbstractMailReceiver receiver = new ImapMailReceiver(); receiver = spy(receiver); receiver.setBeanFactory(mock(BeanFactory.class)); receiver.afterPropertiesSet(); Field folderField = AbstractMailReceiver.class.getDeclaredField("folder"); folderField.setAccessible(true); Folder folder = mock(IMAPFolder.class); given(folder.getPermanentFlags()).willReturn(new Flags(Flags.Flag.USER)); folderField.set(receiver, folder); willAnswer(invocation -> true).given(folder).isOpen(); willAnswer(invocation -> null).given(receiver).openFolder(); DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter); adapterAccessor.setPropertyValue("mailReceiver", receiver); MimeMessage mailMessage = mock(MimeMessage.class); Flags flags = mock(Flags.class); given(mailMessage.getFlags()).willReturn(flags); final Message[] messages = new Message[] { mailMessage }; willAnswer(invocation -> messages).given(receiver).searchForNewMessages(); willAnswer(invocation -> null).given(receiver).fetchMessages(messages); adapter.start(); org.springframework.messaging.Message<?> replMessage = errorChannel.receive(10000); assertNotNull(replMessage); assertEquals("Failed", ((Exception) replMessage.getPayload()).getCause().getMessage()); adapter.stop(); context.close(); } @SuppressWarnings("resource") @Test public void testNoInitialIdleDelayWhenRecentNotSupported() throws Exception { ConfigurableApplicationContext 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.setBeanFactory(mock(BeanFactory.class)); receiver.afterPropertiesSet(); final IMAPFolder folder = mock(IMAPFolder.class); given(folder.getPermanentFlags()).willReturn(new Flags(Flags.Flag.USER)); given(folder.isOpen()).willReturn(false).willReturn(true); given(folder.exists()).willReturn(true); DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter); adapterAccessor.setPropertyValue("mailReceiver", receiver); Field storeField = AbstractMailReceiver.class.getDeclaredField("store"); storeField.setAccessible(true); Store store = mock(Store.class); given(store.isConnected()).willReturn(true); given(store.getFolder(Mockito.any(URLName.class))).willReturn(folder); storeField.set(receiver, store); willAnswer(invocation -> folder).given(receiver).getFolder(); MimeMessage mailMessage = mock(MimeMessage.class); Flags flags = mock(Flags.class); given(mailMessage.getFlags()).willReturn(flags); final Message[] messages = new Message[] { mailMessage }; final AtomicInteger shouldFindMessagesCounter = new AtomicInteger(2); willAnswer(invocation -> { /* * 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]; } }).given(receiver).searchForNewMessages(); willAnswer(invocation -> null).given(receiver).fetchMessages(messages); willAnswer(invocation -> { Thread.sleep(5000); shouldFindMessagesCounter.set(1); return null; }).given(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)); adapter.stop(); context.close(); } @SuppressWarnings("resource") @Test public void testInitialIdleDelayWhenRecentIsSupported() throws Exception { ConfigurableApplicationContext 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.setBeanFactory(mock(BeanFactory.class)); receiver.afterPropertiesSet(); final IMAPFolder folder = mock(IMAPFolder.class); given(folder.getPermanentFlags()).willReturn(new Flags(Flags.Flag.RECENT)); given(folder.isOpen()).willReturn(false).willReturn(true); given(folder.exists()).willReturn(true); DirectFieldAccessor adapterAccessor = new DirectFieldAccessor(adapter); adapterAccessor.setPropertyValue("mailReceiver", receiver); Field storeField = AbstractMailReceiver.class.getDeclaredField("store"); storeField.setAccessible(true); Store store = mock(Store.class); given(store.isConnected()).willReturn(true); given(store.getFolder(Mockito.any(URLName.class))).willReturn(folder); storeField.set(receiver, store); willAnswer(invocation -> folder).given(receiver).getFolder(); MimeMessage mailMessage = mock(MimeMessage.class); Flags flags = mock(Flags.class); given(mailMessage.getFlags()).willReturn(flags); final Message[] messages = new Message[] { mailMessage }; willAnswer(invocation -> messages).given(receiver).searchForNewMessages(); willAnswer(invocation -> null).given(receiver).fetchMessages(messages); final CountDownLatch idles = new CountDownLatch(2); willAnswer(invocation -> { idles.countDown(); Thread.sleep(5000); return null; }).given(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)); adapter.stop(); context.close(); } @Test public void testConnectionException() throws Exception { ImapMailReceiver mailReceiver = new ImapMailReceiver("imap:foo"); ImapIdleChannelAdapter adapter = new ImapIdleChannelAdapter(mailReceiver); final AtomicReference<ImapIdleExceptionEvent> theEvent = new AtomicReference<ImapIdleExceptionEvent>(); final CountDownLatch latch = new CountDownLatch(1); adapter.setApplicationEventPublisher(event -> { assertNull("only one event expected", theEvent.get()); theEvent.set((ImapIdleExceptionEvent) event); latch.countDown(); }); ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); taskScheduler.initialize(); adapter.setTaskScheduler(taskScheduler); adapter.start(); assertTrue(latch.await(10, TimeUnit.SECONDS)); assertTrue(theEvent.get().toString().endsWith("cause=java.lang.IllegalStateException: Failure in 'idle' task. Will resubmit.]")); } @Test // see INT-1801 public void testImapLifecycleForRaceCondition() throws Exception { for (int i = 0; i < 1000; i++) { final ImapMailReceiver receiver = new ImapMailReceiver("imap://foo"); Store store = mock(Store.class); Folder folder = mock(Folder.class); given(folder.exists()).willReturn(true); given(folder.isOpen()).willReturn(true); given(folder.search((SearchTerm) Mockito.any())).willReturn(new Message[] { }); given(store.getFolder(Mockito.any(URLName.class))).willReturn(folder); given(folder.getPermanentFlags()).willReturn(new Flags(Flags.Flag.USER)); DirectFieldAccessor df = new DirectFieldAccessor(receiver); df.setPropertyValue("store", store); receiver.setBeanFactory(mock(BeanFactory.class)); receiver.afterPropertiesSet(); new Thread(() -> { try { receiver.receive(); } catch (javax.mail.MessagingException e) { if (e.getCause() instanceof NullPointerException) { failed.getAndIncrement(); } } }).start(); new Thread(() -> { try { receiver.destroy(); } catch (Exception ignore) { // ignore } }).start(); } assertEquals(0, failed.get()); } @Test public void testAttachments() throws Exception { final ImapMailReceiver receiver = new ImapMailReceiver("imap://foo"); Folder folder = testAttachmentsGuts(receiver); Message[] messages = (Message[]) receiver.receive(); Object content = messages[0].getContent(); assertEquals("bar", ((Multipart) content).getBodyPart(0).getContent().toString().trim()); assertEquals("foo", ((Multipart) content).getBodyPart(1).getContent().toString().trim()); assertSame(folder, messages[0].getFolder()); } @Test public void testAttachmentsWithMappingMultiAsBytes() throws Exception { final ImapMailReceiver receiver = new ImapMailReceiver("imap://foo"); receiver.setHeaderMapper(new DefaultMailHeaderMapper()); testAttachmentsGuts(receiver); org.springframework.messaging.Message<?>[] messages = (org.springframework.messaging.Message<?>[]) receiver .receive(); org.springframework.messaging.Message<?> received = messages[0]; Object content = received.getPayload(); assertThat(content, instanceOf(byte[].class)); assertThat((String) received.getHeaders().get(MailHeaders.CONTENT_TYPE), equalTo("multipart/mixed;\r\n boundary=\"------------040903000701040401040200\"")); assertThat((String) received.getHeaders().get(MessageHeaders.CONTENT_TYPE), equalTo("application/octet-stream")); } @Test public void testAttachmentsWithMapping() throws Exception { final ImapMailReceiver receiver = new ImapMailReceiver("imap://foo"); receiver.setHeaderMapper(new DefaultMailHeaderMapper()); receiver.setEmbeddedPartsAsBytes(false); testAttachmentsGuts(receiver); org.springframework.messaging.Message<?>[] messages = (org.springframework.messaging.Message<?>[]) receiver .receive(); Object content = messages[0].getPayload(); assertThat(content, instanceOf(Multipart.class)); assertEquals("bar", ((Multipart) content).getBodyPart(0).getContent().toString().trim()); assertEquals("foo", ((Multipart) content).getBodyPart(1).getContent().toString().trim()); } private Folder testAttachmentsGuts(final ImapMailReceiver receiver) throws MessagingException, IOException { Store store = mock(Store.class); Folder folder = mock(Folder.class); given(folder.exists()).willReturn(true); given(folder.isOpen()).willReturn(true); Message message = new MimeMessage(null, new ClassPathResource("test.mail").getInputStream()); given(folder.search((SearchTerm) Mockito.any())).willReturn(new Message[] { message }); given(store.getFolder(Mockito.any(URLName.class))).willReturn(folder); given(folder.getPermanentFlags()).willReturn(new Flags(Flags.Flag.USER)); DirectFieldAccessor df = new DirectFieldAccessor(receiver); df.setPropertyValue("store", store); receiver.setBeanFactory(mock(BeanFactory.class)); receiver.afterPropertiesSet(); return folder; } @Test public void testExecShutdown() { ImapIdleChannelAdapter adapter = new ImapIdleChannelAdapter(new ImapMailReceiver()); ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); taskScheduler.initialize(); adapter.setTaskScheduler(taskScheduler); adapter.start(); ExecutorService exec = TestUtils.getPropertyValue(adapter, "sendingTaskExecutor", ExecutorService.class); adapter.stop(); assertTrue(exec.isShutdown()); adapter.start(); exec = TestUtils.getPropertyValue(adapter, "sendingTaskExecutor", ExecutorService.class); adapter.stop(); assertTrue(exec.isShutdown()); } @Test public void testNullMessages() throws Exception { Message message1 = mock(Message.class); Message message2 = mock(Message.class); final Message[] messages1 = new Message[] { null, null, message1 }; final Message[] messages2 = new Message[] { message2 }; final SearchTermStrategy searchTermStrategy = mock(SearchTermStrategy.class); class TestReceiver extends ImapMailReceiver { private boolean firstDone; TestReceiver() { setSearchTermStrategy(searchTermStrategy); } @Override protected Folder getFolder() { Folder folder = mock(Folder.class); given(folder.isOpen()).willReturn(true); try { given(folder.getMessages()) .willReturn(!this.firstDone ? messages1 : messages2); } catch (MessagingException e) { } return folder; } @Override public Message[] receive() throws MessagingException { Message[] messages = searchForNewMessages(); this.firstDone = true; return messages; } } ImapMailReceiver receiver = new TestReceiver(); Message[] received = (Message[]) receiver.receive(); assertEquals(1, received.length); assertSame(message1, received[0]); received = (Message[]) receiver.receive(); assertEquals(1, received.length); assertSame(messages2, received); assertSame(message2, received[0]); } @Test public void testIdleReconnects() throws Exception { ImapMailReceiver receiver = spy(new ImapMailReceiver("imap:foo")); receiver.setBeanFactory(mock(BeanFactory.class)); receiver.afterPropertiesSet(); IMAPFolder folder = mock(IMAPFolder.class); given(folder.getPermanentFlags()).willReturn(new Flags(Flags.Flag.USER)); given(folder.isOpen()).willReturn(false).willReturn(true); given(folder.exists()).willReturn(true); given(folder.hasNewMessages()).willReturn(true); Field storeField = AbstractMailReceiver.class.getDeclaredField("store"); storeField.setAccessible(true); Store store = mock(Store.class); given(store.isConnected()).willReturn(false); given(store.getFolder(Mockito.any(URLName.class))).willReturn(folder); storeField.set(receiver, store); ImapIdleChannelAdapter adapter = new ImapIdleChannelAdapter(receiver); Log logger = spy(TestUtils.getPropertyValue(adapter, "logger", Log.class)); new DirectFieldAccessor(adapter).setPropertyValue("logger", logger); willDoNothing().given(logger).warn(anyString(), any(Throwable.class)); willAnswer(i -> { i.callRealMethod(); throw new FolderClosedException(folder, "test"); }).given(receiver).waitForNewMessages(); ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); taskScheduler.initialize(); adapter.setTaskScheduler(taskScheduler); adapter.setReconnectDelay(50); adapter.afterPropertiesSet(); final CountDownLatch latch = new CountDownLatch(3); adapter.setApplicationEventPublisher(e -> { latch.countDown(); }); adapter.start(); assertTrue(latch.await(60, TimeUnit.SECONDS)); verify(store, atLeast(3)).connect(); taskScheduler.shutdown(); } private void setUpScheduler(ImapMailReceiver mailReceiver, ThreadPoolTaskScheduler taskScheduler) { taskScheduler.setPoolSize(5); taskScheduler.initialize(); BeanFactory bf = mock(BeanFactory.class); given(bf.containsBean("taskScheduler")).willReturn(true); given(bf.getBean("taskScheduler", TaskScheduler.class)).willReturn(taskScheduler); mailReceiver.setBeanFactory(bf); } }