diff --git a/spring-integration-mail/.project b/spring-integration-mail/.project index d8c432b36e..0f6d43ac2a 100644 --- a/spring-integration-mail/.project +++ b/spring-integration-mail/.project @@ -15,8 +15,14 @@ + + org.springframework.ide.eclipse.core.springbuilder + + + + org.springframework.ide.eclipse.core.springnature org.maven.ide.eclipse.maven2Nature org.eclipse.jdt.core.javanature diff --git a/spring-integration-mail/pom.xml b/spring-integration-mail/pom.xml index 2aafa0b8f8..577ba2557d 100644 --- a/spring-integration-mail/pom.xml +++ b/spring-integration-mail/pom.xml @@ -50,6 +50,10 @@ org.easymock easymockclassextension + + org.mockito + mockito-all + org.springframework spring-test diff --git a/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java b/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java index c9d1bac5fc..3b89672fce 100755 --- a/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java +++ b/spring-integration-mail/src/main/java/org/springframework/integration/mail/AbstractMailReceiver.java @@ -27,6 +27,7 @@ import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Store; import javax.mail.URLName; +import javax.mail.Flags.Flag; import javax.mail.internet.MimeMessage; import org.apache.commons.logging.Log; @@ -42,6 +43,7 @@ import org.springframework.util.Assert; * @author Jonas Partner * @author Mark Fisher * @author Iwein Fuld + * @author Oleg Zhurakousky */ public abstract class AbstractMailReceiver implements MailReceiver, DisposableBean { @@ -60,6 +62,8 @@ public abstract class AbstractMailReceiver implements MailReceiver, DisposableBe private volatile Folder folder; private volatile boolean shouldDeleteMessages = false; + + private volatile boolean shouldMarkMessagesAsRead = false; private volatile Properties javaMailProperties = new Properties(); @@ -143,8 +147,21 @@ public abstract class AbstractMailReceiver implements MailReceiver, DisposableBe * Specify whether mail messages should be deleted after retrieval. */ public void setShouldDeleteMessages(boolean shouldDeleteMessages) { + if (this.shouldMarkMessagesAsRead && shouldDeleteMessages){ + throw new IllegalArgumentException("setting both 'shouldDeleteMessages' and 'shouldMarkMessagesAsRead' to true is not allowed"); + } this.shouldDeleteMessages = shouldDeleteMessages; } + public boolean isShouldMarkMessagesAsRead() { + return shouldMarkMessagesAsRead; + } + + public void setShouldMarkMessagesAsRead(boolean shouldMarkMessagesAsRead) { + if (this.shouldDeleteMessages && shouldMarkMessagesAsRead){ + throw new IllegalArgumentException("setting both 'shouldDeleteMessages' and 'shouldMarkMessagesAsRead' to true is not allowed"); + } + this.shouldMarkMessagesAsRead = shouldMarkMessagesAsRead; + } /** * Indicates whether the mail messages should be deleted after being received. @@ -204,10 +221,10 @@ public abstract class AbstractMailReceiver implements MailReceiver, DisposableBe if (logger.isDebugEnabled()) { logger.debug("opening folder [" + MailTransportUtils.toPasswordProtectedString(this.url) + "]"); } - if (this.shouldDeleteMessages()) { + if (this.shouldDeleteMessages() || this.shouldMarkMessagesAsRead) { this.folder.open(Folder.READ_WRITE); } - else { + else { this.folder.open(Folder.READ_ONLY); } } @@ -230,13 +247,17 @@ public abstract class AbstractMailReceiver implements MailReceiver, DisposableBe if (messages.length > 0) { this.fetchMessages(messages); } - if (this.shouldDeleteMessages()) { - this.deleteMessages(messages); - } + Message[] copiedMessages = new Message[messages.length]; for (int i = 0; i < messages.length; i++) { + if (this.isShouldMarkMessagesAsRead()){ + messages[i].setFlag(Flag.SEEN, true); + } copiedMessages[i] = new MimeMessage((MimeMessage) messages[i]); } + if (this.shouldDeleteMessages()) { + this.deleteMessages(messages); + } return copiedMessages; } catch (Exception e) { 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 db65c081d7..78ba075fce 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 @@ -25,6 +25,7 @@ import javax.mail.event.MessageCountEvent; import javax.mail.event.MessageCountListener; import javax.mail.search.AndTerm; import javax.mail.search.FlagTerm; +import javax.mail.search.NotTerm; import javax.mail.search.SearchTerm; import org.springframework.util.Assert; @@ -41,6 +42,7 @@ import com.sun.mail.imap.IMAPFolder; * * @author Arjen Poutsma * @author Mark Fisher + * @author Oleg Zhurakousky */ public class ImapMailReceiver extends AbstractMailReceiver { @@ -99,6 +101,11 @@ public class ImapMailReceiver extends AbstractMailReceiver { @Override protected Message[] searchForNewMessages() throws MessagingException { Flags supportedFlags = this.getFolder().getPermanentFlags(); + SearchTerm searchTerm = this.compileSearchTerms(supportedFlags); + return searchTerm != null ? this.getFolder().search(searchTerm) : this.getFolder().getMessages(); + } + + private SearchTerm compileSearchTerms(Flags supportedFlags){ SearchTerm searchTerm = null; if (supportedFlags != null) { if (supportedFlags.contains(Flags.Flag.RECENT)) { @@ -123,7 +130,16 @@ public class ImapMailReceiver extends AbstractMailReceiver { } } } - return searchTerm != null ? this.getFolder().search(searchTerm) : this.getFolder().getMessages(); + if (searchTerm == null){ + if (this.isShouldMarkMessagesAsRead()){ + searchTerm = new NotTerm( new FlagTerm(new Flags(Flags.Flag.SEEN), true) ); + } + } else { + if (this.isShouldMarkMessagesAsRead()){ + searchTerm = new AndTerm(searchTerm, new NotTerm( new FlagTerm(new Flags(Flags.Flag.SEEN), true) )); + } + } + return searchTerm; } diff --git a/spring-integration-mail/src/test/java/log4j.properties b/spring-integration-mail/src/test/java/log4j.properties new file mode 100644 index 0000000000..cf091a2e8e --- /dev/null +++ b/spring-integration-mail/src/test/java/log4j.properties @@ -0,0 +1,8 @@ +log4j.rootCategory=DEBUG, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%c{1}: %m%n + +log4j.category.org.springframework.integration=WARN +log4j.category.org.springframework.integration.file=WARN diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/AbstractMailReceiverTests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/AbstractMailReceiverTests.java new file mode 100644 index 0000000000..b0b5a032d4 --- /dev/null +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/AbstractMailReceiverTests.java @@ -0,0 +1,116 @@ +/* + * Copyright 2002-2010 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 javax.mail.Message; +import javax.mail.Flags.Flag; +import javax.mail.internet.MimeMessage; + +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * @author Oleg Zhurakousky + * + */ +public class AbstractMailReceiverTests { + + @Test(expected=IllegalArgumentException.class) + public void validateDeleteAndReadIsNotAllowed(){ + AbstractMailReceiver receiver = new ImapMailReceiver(); + receiver.setShouldDeleteMessages(true); + receiver.setShouldMarkMessagesAsRead(true); + } + @Test + public void validateDeleteOrReadIsAllowed_Read(){ + AbstractMailReceiver receiver = new ImapMailReceiver(); + receiver.setShouldDeleteMessages(false); + receiver.setShouldMarkMessagesAsRead(true); + } + @Test + public void validateDeleteOrReadIsAllowed_Delete(){ + AbstractMailReceiver receiver = new ImapMailReceiver(); + receiver.setShouldDeleteMessages(true); + receiver.setShouldMarkMessagesAsRead(false); + } + + @Test + public void receieveAndMarkAsRead() throws Exception{ + AbstractMailReceiver receiver = new ImapMailReceiver(); + receiver.setShouldMarkMessagesAsRead(true); + receiver = spy(receiver); + Message msg1 = mock(MimeMessage.class); + Message msg2 = mock(MimeMessage.class); + final Message[] messages = new Message[]{msg1, msg2}; + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) throws Throwable { + // just to avoid the exception + return null; + } + }).when(receiver).openFolder(); + + 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); + receiver.receive(); + verify(msg1, times(1)).setFlag(Flag.SEEN, true); + verify(msg2, times(1)).setFlag(Flag.SEEN, true); + } + @Test + public void receieveAndDontMarkAsRead() throws Exception{ + AbstractMailReceiver receiver = new ImapMailReceiver(); + receiver = spy(receiver); + Message msg1 = mock(MimeMessage.class); + Message msg2 = mock(MimeMessage.class); + final Message[] messages = new Message[]{msg1, msg2}; + doAnswer(new Answer() { + public Object answer(InvocationOnMock invocation) throws Throwable { + // just to avoid the exception + return null; + } + }).when(receiver).openFolder(); + + 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); + receiver.receive(); + verify(msg1, times(0)).setFlag(Flag.SEEN, true); + verify(msg2, times(0)).setFlag(Flag.SEEN, true); + } +} diff --git a/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailSearchTermsTests.java b/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailSearchTermsTests.java new file mode 100644 index 0000000000..326e2d8b49 --- /dev/null +++ b/spring-integration-mail/src/test/java/org/springframework/integration/mail/ImapMailSearchTermsTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2010 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 java.lang.reflect.Method; + +import javax.mail.Flags; +import javax.mail.Flags.Flag; +import javax.mail.search.AndTerm; +import javax.mail.search.FlagTerm; +import javax.mail.search.NotTerm; +import javax.mail.search.SearchTerm; + +import org.junit.Test; +import org.springframework.util.ReflectionUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Oleg Zhurakousky + * + */ +public class ImapMailSearchTermsTests { + + @Test + public void validateSearchTermsWhenShouldMarkAsReadNoExistingFlags() throws Exception { + ImapMailReceiver receiver = new ImapMailReceiver(); + receiver.setShouldMarkMessagesAsRead(true); + Method compileSearchTerms = ReflectionUtils.findMethod(receiver.getClass(), "compileSearchTerms", Flags.class); + compileSearchTerms.setAccessible(true); + Flags flags = new Flags(); + SearchTerm searchTerms = (SearchTerm) compileSearchTerms.invoke(receiver, flags); + assertTrue(searchTerms instanceof NotTerm); + NotTerm notTerm = (NotTerm) searchTerms; + assertTrue(((FlagTerm)notTerm.getTerm()).getFlags().contains(Flag.SEEN)); + } + @Test + public void validateSearchTermsWhenShouldMarkAsReadWithExistingFlags() throws Exception { + ImapMailReceiver receiver = new ImapMailReceiver(); + receiver.setShouldMarkMessagesAsRead(true); + Method compileSearchTerms = ReflectionUtils.findMethod(receiver.getClass(), "compileSearchTerms", Flags.class); + compileSearchTerms.setAccessible(true); + Flags flags = new Flags(); + flags.add(Flag.ANSWERED); + SearchTerm searchTerms = (SearchTerm) compileSearchTerms.invoke(receiver, flags); + assertTrue(searchTerms instanceof AndTerm); + AndTerm andTerm = (AndTerm) searchTerms; + SearchTerm[] terms = andTerm.getTerms(); + assertEquals(2, terms.length); + NotTerm notTerm = (NotTerm) terms[1]; + assertTrue(((FlagTerm)notTerm.getTerm()).getFlags().contains(Flag.SEEN)); + } + + @Test + public void validateSearchTermsWhenShouldNotMarkAsReadNoExistingFlags() throws Exception { + ImapMailReceiver receiver = new ImapMailReceiver(); + Method compileSearchTerms = ReflectionUtils.findMethod(receiver.getClass(), "compileSearchTerms", Flags.class); + compileSearchTerms.setAccessible(true); + Flags flags = new Flags(); + SearchTerm searchTerms = (SearchTerm) compileSearchTerms.invoke(receiver, flags); + assertNull(searchTerms); + } + @Test + public void validateSearchTermsWhenShouldNotMarkAsReadWithExistingFlags() throws Exception { + ImapMailReceiver receiver = new ImapMailReceiver(); + Method compileSearchTerms = ReflectionUtils.findMethod(receiver.getClass(), "compileSearchTerms", Flags.class); + compileSearchTerms.setAccessible(true); + Flags flags = new Flags(); + flags.add(Flag.ANSWERED); + SearchTerm searchTerms = (SearchTerm) compileSearchTerms.invoke(receiver, flags); + assertTrue(searchTerms instanceof FlagTerm); + FlagTerm flagTerm = (FlagTerm) searchTerms; + assertTrue(flagTerm.getFlags().contains(Flag.ANSWERED)); + } +}