/*
* 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);
}
}