org.springframework.integration.mail.AbstractMailReceiver.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.mail.AbstractMailReceiver.java

Source

/*
 * Copyright 2002-2013 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.LinkedList;
import java.util.List;
import java.util.Properties;

import javax.mail.Authenticator;
import javax.mail.FetchProfile;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.URLName;
import javax.mail.internet.MimeMessage;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.util.Assert;

/**
 * Base class for {@link MailReceiver} implementations.
 *
 * @author Arjen Poutsma
 * @author Jonas Partner
 * @author Mark Fisher
 * @author Iwein Fuld
 * @author Oleg Zhurakousky
 * @author Gary Russell
 */
public abstract class AbstractMailReceiver extends IntegrationObjectSupport
        implements MailReceiver, DisposableBean {

    public final static String SI_USER_FLAG = "spring-integration-mail-adapter";

    protected final Log logger = LogFactory.getLog(this.getClass());

    private final URLName url;

    private volatile String protocol;

    private volatile int maxFetchSize = -1;

    private volatile Session session;

    private volatile Store store;

    private volatile Folder folder;

    private volatile boolean shouldDeleteMessages;

    protected volatile int folderOpenMode = Folder.READ_ONLY;

    private volatile Properties javaMailProperties = new Properties();

    private volatile Authenticator javaMailAuthenticator;

    private volatile StandardEvaluationContext evaluationContext;

    private volatile Expression selectorExpression;

    protected volatile boolean initialized;

    private final Object folderMonitor = new Object();

    public AbstractMailReceiver() {
        this.url = null;
    }

    public AbstractMailReceiver(URLName urlName) {
        Assert.notNull(urlName, "urlName must not be null");
        this.url = urlName;
    }

    public AbstractMailReceiver(String url) {
        if (url != null) {
            this.url = new URLName(url);
        } else {
            this.url = null;
        }
    }

    public void setSelectorExpression(Expression selectorExpression) {
        this.selectorExpression = selectorExpression;
    }

    public void setProtocol(String protocol) {
        if (this.url != null) {
            Assert.isTrue(this.url.getProtocol().equals(protocol),
                    "The 'protocol' does not match that provided by the Store URI.");
        }
        this.protocol = protocol;
    }

    /**
     * Set the {@link Session}. Otherwise, the Session will be created by invocation of
     * {@link Session#getInstance(Properties)} or {@link Session#getInstance(Properties, Authenticator)}.
     *
     * @see #setJavaMailProperties(Properties)
     * @see #setJavaMailAuthenticator(Authenticator)
     */
    public void setSession(Session session) {
        Assert.notNull(session, "Session must not be null");
        this.session = session;
    }

    /**
     * A new {@link Session} will be created with these properties (and the JavaMailAuthenticator if provided).
     * Use either this method or {@link #setSession}, but not both.
     *
     * @see #setJavaMailAuthenticator(Authenticator)
     * @see #setSession(Session)
     */
    public void setJavaMailProperties(Properties javaMailProperties) {
        this.javaMailProperties = javaMailProperties;
    }

    /**
     * Optional, sets the Authenticator to be used to obtain a session. This will not be used if
     * {@link AbstractMailReceiver#setSession} has been used to configure the {@link Session} directly.
     *
     * @see #setSession(Session)
     */
    public void setJavaMailAuthenticator(Authenticator javaMailAuthenticator) {
        this.javaMailAuthenticator = javaMailAuthenticator;
    }

    /**
     * Specify the maximum number of Messages to fetch per call to {@link #receive()}.
     */
    public void setMaxFetchSize(int maxFetchSize) {
        this.maxFetchSize = maxFetchSize;
    }

    /**
     * Specify whether mail messages should be deleted after retrieval.
     */
    public void setShouldDeleteMessages(boolean shouldDeleteMessages) {
        this.shouldDeleteMessages = shouldDeleteMessages;
    }

    /**
     * Indicates whether the mail messages should be deleted after being received.
     */
    protected boolean shouldDeleteMessages() {
        return this.shouldDeleteMessages;
    }

    protected Folder getFolder() {
        return this.folder;
    }

    /**
     * Subclasses must implement this method to return new mail messages.
     */
    protected abstract Message[] searchForNewMessages() throws MessagingException;

    private void openSession() throws MessagingException {
        if (this.session == null) {
            if (this.javaMailAuthenticator != null) {
                this.session = Session.getInstance(this.javaMailProperties, this.javaMailAuthenticator);
            } else {
                this.session = Session.getInstance(this.javaMailProperties);
            }
        }
        if (this.store == null) {
            if (this.url != null) {
                this.store = this.session.getStore(this.url);
            } else if (this.protocol != null) {
                this.store = this.session.getStore(this.protocol);
            } else {
                this.store = this.session.getStore();
            }
        }
        if (!this.store.isConnected()) {
            if (logger.isDebugEnabled()) {
                logger.debug(
                        "connecting to store [" + MailTransportUtils.toPasswordProtectedString(this.url) + "]");
            }
            this.store.connect();
        }
    }

    protected void openFolder() throws MessagingException {
        this.openSession();
        if (this.folder == null) {
            this.folder = obtainFolderInstance();
        }
        if (this.folder == null || !this.folder.exists()) {
            throw new IllegalStateException("no such folder [" + this.url.getFile() + "]");
        }
        if (this.folder.isOpen()) {
            return;
        }
        if (logger.isDebugEnabled()) {
            logger.debug("opening folder [" + MailTransportUtils.toPasswordProtectedString(this.url) + "]");
        }
        this.folder.open(this.folderOpenMode);
    }

    private Folder obtainFolderInstance() throws MessagingException {
        return this.store.getFolder(this.url);
    }

    public Message[] receive() throws javax.mail.MessagingException {
        synchronized (this.folderMonitor) {
            try {
                this.openFolder();
                if (logger.isInfoEnabled()) {
                    logger.info("attempting to receive mail from folder [" + this.getFolder().getFullName() + "]");
                }
                Message[] messages = this.searchForNewMessages();
                if (this.maxFetchSize > 0 && messages.length > this.maxFetchSize) {
                    Message[] reducedMessages = new Message[this.maxFetchSize];
                    System.arraycopy(messages, 0, reducedMessages, 0, this.maxFetchSize);
                    messages = reducedMessages;
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("found " + messages.length + " new messages");
                }
                if (messages.length > 0) {
                    this.fetchMessages(messages);
                }

                if (logger.isDebugEnabled()) {
                    logger.debug("Received " + messages.length + " messages");
                }

                Message[] filteredMessages = this.filterMessagesThruSelector(messages);

                this.postProcessFilteredMessages(filteredMessages);

                return filteredMessages;
            } finally {
                MailTransportUtils.closeFolder(this.folder, this.shouldDeleteMessages);
            }
        }
    }

    private void postProcessFilteredMessages(Message[] filteredMessages) throws MessagingException {
        this.setMessageFlags(filteredMessages);

        if (this.shouldDeleteMessages()) {
            this.deleteMessages(filteredMessages);
        }
        // Copy messages to cause an eager fetch
        for (int i = 0; i < filteredMessages.length; i++) {
            MimeMessage mimeMessage = new IntegrationMimeMessage((MimeMessage) filteredMessages[i]);
            filteredMessages[i] = mimeMessage;
        }
    }

    private void setMessageFlags(Message[] filteredMessages) throws MessagingException {
        boolean recentFlagSupported = false;

        Flags flags = this.getFolder().getPermanentFlags();

        if (flags != null) {
            recentFlagSupported = flags.contains(Flags.Flag.RECENT);
        }
        for (Message message : filteredMessages) {
            if (!recentFlagSupported) {
                if (flags != null && flags.contains(Flags.Flag.USER)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("USER flags are supported by this mail server. Flagging message with '"
                                + SI_USER_FLAG + "' user flag");
                    }
                    Flags siFlags = new Flags();
                    siFlags.add(SI_USER_FLAG);
                    message.setFlags(siFlags, true);
                } else {
                    if (logger.isDebugEnabled()) {
                        logger.debug(
                                "USER flags are not supported by this mail server. Flagging message with system flag");
                    }
                    message.setFlag(Flags.Flag.FLAGGED, true);
                }
            }
            this.setAdditionalFlags(message);
        }
    }

    /**
     * Will filter Messages thru selector. Messages that did not pass selector filtering criteria
     * will be filtered out and remain on the server as never touched.
     */
    private Message[] filterMessagesThruSelector(Message[] messages) throws MessagingException {
        List<Message> filteredMessages = new LinkedList<Message>();
        for (int i = 0; i < messages.length; i++) {
            MimeMessage message = (MimeMessage) messages[i];
            if (this.selectorExpression != null) {
                if (this.selectorExpression.getValue(this.evaluationContext, message, Boolean.class)) {
                    filteredMessages.add(message);
                } else {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Fetched email with subject '" + message.getSubject()
                                + "' will be discarded by the matching filter"
                                + " and will not be flagged as SEEN.");
                    }
                }
            } else {
                filteredMessages.add(message);
            }
        }
        return filteredMessages.toArray(new Message[filteredMessages.size()]);
    }

    /**
     * Fetches the specified messages from this receiver's folder. Default
     * implementation {@link Folder#fetch(Message[], FetchProfile) fetches}
     * every {@link javax.mail.FetchProfile.Item}.
     *
     * @param messages the messages to fetch
     * @throws MessagingException in case of JavaMail errors
     */
    protected void fetchMessages(Message[] messages) throws MessagingException {
        FetchProfile contentsProfile = new FetchProfile();
        contentsProfile.add(FetchProfile.Item.ENVELOPE);
        contentsProfile.add(FetchProfile.Item.CONTENT_INFO);
        contentsProfile.add(FetchProfile.Item.FLAGS);
        this.folder.fetch(messages, contentsProfile);
    }

    /**
     * Deletes the given messages from this receiver's folder.
     *
     * @param messages the messages to delete
     * @throws MessagingException in case of JavaMail errors
     */
    protected void deleteMessages(Message[] messages) throws MessagingException {
        for (int i = 0; i < messages.length; i++) {
            messages[i].setFlag(Flags.Flag.DELETED, true);
        }
    }

    /**
     * Optional method allowing you to set additional flags.
     * Currently only implemented in IMapMailReceiver.
     *
     * @param message
     * @throws MessagingException
     */
    protected void setAdditionalFlags(Message message) throws MessagingException {
    }

    public void destroy() throws Exception {
        synchronized (this.folderMonitor) {
            MailTransportUtils.closeFolder(this.folder, this.shouldDeleteMessages);
            MailTransportUtils.closeService(this.store);
            this.folder = null;
            this.store = null;
            this.initialized = false;
        }
    }

    @Override
    protected void onInit() throws Exception {
        super.onInit();
        this.folderOpenMode = Folder.READ_WRITE;
        this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory());
        this.initialized = true;
    }

    @Override
    public String toString() {
        return this.url.toString();
    }

    Store getStore() {
        return this.store;
    }

    /**
     * Since we copy the message to eagerly fetch the message, it has no folder.
     * However, we need to make a folder available in case the user wants to
     * perform operations on the message in the folder later in the flow.
     * @author Gary Russell
     * @since 2.2
     *
     */
    private class IntegrationMimeMessage extends MimeMessage {

        public IntegrationMimeMessage(MimeMessage source) throws MessagingException {
            super(source);
        }

        @Override
        public Folder getFolder() {
            try {
                return AbstractMailReceiver.this.obtainFolderInstance();
            } catch (MessagingException e) {
                throw new org.springframework.integration.MessagingException("Unable to obtain the mail folder", e);
            }
        }

    }
}