org.springframework.integration.mail.ImapMailReceiver Maven / Gradle / Ivy
/*
* Copyright 2002-2016 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.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ScheduledFuture;
import javax.mail.Flags;
import javax.mail.Flags.Flag;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.event.MessageCountAdapter;
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.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.Assert;
import com.sun.mail.imap.IMAPFolder;
/**
* A {@link MailReceiver} implementation for receiving mail messages from a
* mail server that supports the IMAP protocol. In addition to the pollable
* {@link #receive()} method, the {@link #waitForNewMessages()} method provides
* the option of blocking until new messages are available prior to calling
* {@link #receive()}. That option is only available if the server supports
* the {@link IMAPFolder#idle() idle} command.
*
* @author Arjen Poutsma
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
*/
public class ImapMailReceiver extends AbstractMailReceiver {
private static final int DEFAULT_CANCEL_IDLE_INTERVAL = 120000;
private final MessageCountListener messageCountListener = new SimpleMessageCountListener();
private final IdleCanceler idleCanceler = new IdleCanceler();
private volatile boolean shouldMarkMessagesAsRead = true;
private volatile SearchTermStrategy searchTermStrategy = new DefaultSearchTermStrategy();
private volatile long cancelIdleInterval = DEFAULT_CANCEL_IDLE_INTERVAL;
private volatile TaskScheduler scheduler;
private volatile ScheduledFuture> pingTask;
public ImapMailReceiver() {
super();
this.setProtocol("imap");
}
public ImapMailReceiver(String url) {
super(url);
if (url != null) {
Assert.isTrue(url.toLowerCase().startsWith("imap"),
"URL must start with 'imap' for the IMAP Mail receiver.");
}
else {
this.setProtocol("imap");
}
}
/**
* Check if messages should be marked as read.
*
* @return true if messages should be marked as read.
*/
public Boolean isShouldMarkMessagesAsRead() {
return this.shouldMarkMessagesAsRead;
}
/**
* Provides a way to set custom {@link SearchTermStrategy} to compile a {@link SearchTerm}
* to be applied when retrieving mail
*
* @param searchTermStrategy The search term strategy implementation.
*/
public void setSearchTermStrategy(SearchTermStrategy searchTermStrategy) {
Assert.notNull(searchTermStrategy, "'searchTermStrategy' must not be null");
this.searchTermStrategy = searchTermStrategy;
}
/**
* Specify if messages should be marked as read.
*
* @param shouldMarkMessagesAsRead true if messages should be marked as read.
*/
public void setShouldMarkMessagesAsRead(Boolean shouldMarkMessagesAsRead) {
this.shouldMarkMessagesAsRead = shouldMarkMessagesAsRead;
}
/**
* IDLE commands will be terminated after this interval; useful in cases where a connection
* might be silently dropped. A new IDLE will usually immediately be processed. Specified
* in seconds; default 120 (2 minutes). RFC 2177 recommends an interval no larger than 29 minutes.
* @param cancelIdleInterval the cancelIdleInterval to set
* @since 3.0.5
*/
public void setCancelIdleInterval(long cancelIdleInterval) {
this.cancelIdleInterval = cancelIdleInterval * 1000;
}
@Override
protected void onInit() throws Exception {
super.onInit();
this.scheduler = getTaskScheduler();
if (this.scheduler == null) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.initialize();
this.scheduler = scheduler;
}
Properties javaMailProperties = getJavaMailProperties();
for (String name : new String[]{"imap", "imaps"}) {
String peek = "mail." + name + ".peek";
if (javaMailProperties.getProperty(peek) == null) {
javaMailProperties.setProperty(peek, "true");
}
}
}
/**
* This method is unique to the IMAP receiver and only works if IMAP IDLE
* is supported (see RFC 2177 for more detail).
*
* @throws MessagingException Any MessagingException.
*/
public void waitForNewMessages() throws MessagingException {
this.openFolder();
Folder folder = this.getFolder();
Assert.state(folder instanceof IMAPFolder,
"folder is not an instance of [" + IMAPFolder.class.getName() + "]");
IMAPFolder imapFolder = (IMAPFolder) folder;
if (imapFolder.hasNewMessages()) {
return;
}
else if (!folder.getPermanentFlags().contains(Flags.Flag.RECENT)) {
if (searchForNewMessages().length > 0) {
return;
}
}
imapFolder.addMessageCountListener(this.messageCountListener);
try {
this.pingTask = this.scheduler.schedule(this.idleCanceler,
new Date(System.currentTimeMillis() + this.cancelIdleInterval));
imapFolder.idle();
}
finally {
imapFolder.removeMessageCountListener(this.messageCountListener);
if (this.pingTask != null) {
this.pingTask.cancel(true);
}
}
}
/**
* Retrieves new messages from this receiver's folder. This implementation
* creates a {@link SearchTerm} that searches for all messages in the
* folder that are {@link javax.mail.Flags.Flag#RECENT RECENT}, not
* {@link javax.mail.Flags.Flag#ANSWERED ANSWERED}, and not
* {@link javax.mail.Flags.Flag#DELETED DELETED}. The search term is used
* to {@link Folder#search(SearchTerm) search} for new messages.
*
* @return the new messages
* @throws MessagingException in case of JavaMail errors
*/
@Override
protected Message[] searchForNewMessages() throws MessagingException {
Flags supportedFlags = this.getFolder().getPermanentFlags();
SearchTerm searchTerm = this.compileSearchTerms(supportedFlags);
Folder folder = this.getFolder();
if (folder.isOpen()) {
return nullSafeMessages(searchTerm != null ? folder.search(searchTerm) : folder.getMessages());
}
throw new MessagingException("Folder is closed");
}
// INT-3859
private Message[] nullSafeMessages(Message[] messageArray) {
boolean hasNulls = false;
for (Message message : messageArray) {
if (message == null) {
hasNulls = true;
break;
}
}
if (!hasNulls) {
return messageArray;
}
else {
List messages = new ArrayList();
for (Message message : messageArray) {
if (message != null) {
messages.add(message);
}
}
return messages.toArray(new Message[messages.size()]);
}
}
private SearchTerm compileSearchTerms(Flags supportedFlags) {
return this.searchTermStrategy.generateSearchTerm(supportedFlags, this.getFolder());
}
@Override
protected void setAdditionalFlags(Message message) throws MessagingException {
super.setAdditionalFlags(message);
if (this.shouldMarkMessagesAsRead) {
message.setFlag(Flag.SEEN, true);
}
}
private class IdleCanceler implements Runnable {
@Override
public void run() {
try {
Folder folder = getFolder();
logger.debug("Canceling IDLE");
if (folder != null) {
folder.isOpen(); // resets idle state
}
}
catch (Exception ignore) {
}
}
}
/**
* Callback used for handling the event-driven idle response.
*/
private class SimpleMessageCountListener extends MessageCountAdapter {
@Override
public void messagesAdded(MessageCountEvent event) {
Message[] messages = event.getMessages();
if (messages.length > 0) {
// this will return the flow to the idle call
messages[0].getFolder().isOpen();
}
}
}
private class DefaultSearchTermStrategy implements SearchTermStrategy {
@Override
public SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder) {
SearchTerm searchTerm = null;
boolean recentFlagSupported = false;
if (supportedFlags != null) {
recentFlagSupported = supportedFlags.contains(Flags.Flag.RECENT);
if (recentFlagSupported) {
searchTerm = new FlagTerm(new Flags(Flags.Flag.RECENT), true);
}
if (supportedFlags.contains(Flags.Flag.ANSWERED)) {
NotTerm notAnswered = new NotTerm(new FlagTerm(new Flags(Flags.Flag.ANSWERED), true));
if (searchTerm == null) {
searchTerm = notAnswered;
}
else {
searchTerm = new AndTerm(searchTerm, notAnswered);
}
}
if (supportedFlags.contains(Flags.Flag.DELETED)) {
NotTerm notDeleted = new NotTerm(new FlagTerm(new Flags(Flags.Flag.DELETED), true));
if (searchTerm == null) {
searchTerm = notDeleted;
}
else {
searchTerm = new AndTerm(searchTerm, notDeleted);
}
}
if (supportedFlags.contains(Flags.Flag.SEEN)) {
NotTerm notSeen = new NotTerm(new FlagTerm(new Flags(Flags.Flag.SEEN), true));
if (searchTerm == null) {
searchTerm = notSeen;
}
else {
searchTerm = new AndTerm(searchTerm, notSeen);
}
}
}
if (!recentFlagSupported) {
NotTerm notFlagged = null;
if (folder.getPermanentFlags().contains(Flags.Flag.USER)) {
logger.debug("This email server does not support RECENT flag, but it does support " +
"USER flags which will be used to prevent duplicates during email fetch." +
" This receiver instance uses flag: " + getUserFlag());
Flags siFlags = new Flags();
siFlags.add(getUserFlag());
notFlagged = new NotTerm(new FlagTerm(siFlags, true));
}
else {
logger.debug("This email server does not support RECENT or USER flags. " +
"System flag 'Flag.FLAGGED' will be used to prevent duplicates during email fetch.");
notFlagged = new NotTerm(new FlagTerm(new Flags(Flags.Flag.FLAGGED), true));
}
if (searchTerm == null) {
searchTerm = notFlagged;
}
else {
searchTerm = new AndTerm(searchTerm, notFlagged);
}
}
return searchTerm;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy