/*
* (C) Copyright 2006-2009 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* Nuxeo - initial API and implementation
*
* $Id$
*/
package org.nuxeo.ecm.platform.mail.utils;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.CORE_SESSION_KEY;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.EMAIL_PROPERTY_NAME;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.EMAILS_LIMIT_PROPERTY_NAME;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.HOST_PROPERTY_NAME;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.IMAP;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.MIMETYPE_SERVICE_KEY;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PARENT_PATH_KEY;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PASSWORD_PROPERTY_NAME;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PORT_PROPERTY_NAME;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PROTOCOL_TYPE_PROPERTY_NAME;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.SOCKET_FACTORY_FALLBACK_PROPERTY_NAME;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.SOCKET_FACTORY_PORT_PROPERTY_NAME;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.SSL_PROTOCOLS_PROPERTY_NAME;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.STARTTLS_ENABLE_PROPERTY_NAME;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.IMAPS;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.POP3S;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PROTOCOL_TYPE_KEY;
import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.LEAVE_ON_SERVER_KEY;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
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.Flags.Flag;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.platform.mail.action.ExecutionContext;
import org.nuxeo.ecm.platform.mail.action.MessageActionPipe;
import org.nuxeo.ecm.platform.mail.action.Visitor;
import org.nuxeo.ecm.platform.mail.service.MailService;
import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry;
import org.nuxeo.ecm.platform.mail.listener.MailEventListener;
import org.nuxeo.runtime.api.Framework;
import com.sun.mail.imap.IMAPFolder;
import com.sun.mail.imap.IMAPMessage;
/**
* Helper for Mail Core.
*
* @author Catalin Baican
*/
public final class MailCoreHelper {
private static final Log log = LogFactory.getLog(MailEventListener.class);
public static final String PIPE_NAME = "nxmail";
public static final String INBOX = "INBOX";
public static final String DELETED_LIFECYCLE_STATE = "deleted";
public static final long EMAILS_LIMIT_DEFAULT = 100;
private static MailService mailService;
private static MimetypeRegistry mimeService;
public static final String IMAP_DEBUG = "org.nuxeo.mail.imap.debug";
protected static final CopyOnWriteArrayList<String> processingMailBoxes = new CopyOnWriteArrayList<String>();
private MailCoreHelper() {
}
private static MailService getMailService() {
if (mailService == null) {
mailService = Framework.getService(MailService.class);
}
return mailService;
}
private static MimetypeRegistry getMimeService() {
if (mimeService == null) {
mimeService = Framework.getService(MimetypeRegistry.class);
}
return mimeService;
}
/**
* Creates MailMessage documents for every unread mail found in the INBOX. The parameters needed to connect to the
* email INBOX are retrieved from the MailFolder document passed as a parameter.
*/
public static void checkMail(DocumentModel currentMailFolder, CoreSession coreSession) throws MessagingException {
if (processingMailBoxes.addIfAbsent(currentMailFolder.getId())) {
try {
doCheckMail(currentMailFolder, coreSession);
} finally {
processingMailBoxes.remove(currentMailFolder.getId());
}
} else {
log.info("Mailbox " + currentMailFolder.getPathAsString() + " is already being processed");
}
}
protected static void doCheckMail(DocumentModel currentMailFolder, CoreSession coreSession)
throws MessagingException {
String email = (String) currentMailFolder.getPropertyValue(EMAIL_PROPERTY_NAME);
String password = (String) currentMailFolder.getPropertyValue(PASSWORD_PROPERTY_NAME);
if (!StringUtils.isEmpty(email) && !StringUtils.isEmpty(password)) {
mailService = getMailService();
MessageActionPipe pipe = mailService.getPipe(PIPE_NAME);
Visitor visitor = new Visitor(pipe);
Thread.currentThread().setContextClassLoader(Framework.class.getClassLoader());
// initialize context
ExecutionContext initialExecutionContext = new ExecutionContext();
initialExecutionContext.put(MIMETYPE_SERVICE_KEY, getMimeService());
initialExecutionContext.put(PARENT_PATH_KEY, currentMailFolder.getPathAsString());
initialExecutionContext.put(CORE_SESSION_KEY, coreSession);
initialExecutionContext.put(LEAVE_ON_SERVER_KEY, Boolean.TRUE); // TODO should be an attribute in 'protocol'
// schema
Folder rootFolder = null;
Store store = null;
try {
String protocolType = (String) currentMailFolder.getPropertyValue(PROTOCOL_TYPE_PROPERTY_NAME);
initialExecutionContext.put(PROTOCOL_TYPE_KEY, protocolType);
// log.debug(PROTOCOL_TYPE_KEY + ": " + (String) initialExecutionContext.get(PROTOCOL_TYPE_KEY));
String host = (String) currentMailFolder.getPropertyValue(HOST_PROPERTY_NAME);
String port = (String) currentMailFolder.getPropertyValue(PORT_PROPERTY_NAME);
Boolean socketFactoryFallback = (Boolean) currentMailFolder.getPropertyValue(SOCKET_FACTORY_FALLBACK_PROPERTY_NAME);
String socketFactoryPort = (String) currentMailFolder.getPropertyValue(SOCKET_FACTORY_PORT_PROPERTY_NAME);
Boolean starttlsEnable = (Boolean) currentMailFolder.getPropertyValue(STARTTLS_ENABLE_PROPERTY_NAME);
String sslProtocols = (String) currentMailFolder.getPropertyValue(SSL_PROTOCOLS_PROPERTY_NAME);
Long emailsLimit = (Long) currentMailFolder.getPropertyValue(EMAILS_LIMIT_PROPERTY_NAME);
long emailsLimitLongValue = emailsLimit == null ? EMAILS_LIMIT_DEFAULT : emailsLimit.longValue();
String imapDebug = Framework.getProperty(IMAP_DEBUG, "false");
Properties properties = new Properties();
properties.put("mail.store.protocol", protocolType);
// properties.put("mail.host", host);
// Is IMAP connection
if (IMAP.equals(protocolType)) {
properties.put("mail.imap.host", host);
properties.put("mail.imap.port", port);
properties.put("mail.imap.starttls.enable", starttlsEnable.toString());
properties.put("mail.imap.debug", imapDebug);
properties.put("mail.imap.partialfetch", "false");
} else if (IMAPS.equals(protocolType)) {
properties.put("mail.imaps.host", host);
properties.put("mail.imaps.port", port);
properties.put("mail.imaps.starttls.enable", starttlsEnable.toString());
properties.put("mail.imaps.ssl.protocols", sslProtocols);
properties.put("mail.imaps.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
properties.put("mail.imaps.socketFactory.fallback", socketFactoryFallback.toString());
properties.put("mail.imaps.socketFactory.port", socketFactoryPort);
properties.put("mail.imap.partialfetch", "false");
properties.put("mail.imaps.partialfetch", "false");
} else if (POP3S.equals(protocolType)) {
properties.put("mail.pop3s.host", host);
properties.put("mail.pop3s.port", port);
properties.put("mail.pop3s.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
properties.put("mail.pop3s.socketFactory.fallback", socketFactoryFallback.toString());
properties.put("mail.pop3s.socketFactory.port", socketFactoryPort);
properties.put("mail.pop3s.ssl.protocols", sslProtocols);
} else {
// Is POP3 connection
properties.put("mail.pop3.host", host);
properties.put("mail.pop3.port", port);
}
properties.put("user", email);
properties.put("password", password);
Session session = Session.getInstance(properties);
store = session.getStore();
store.connect(email, password);
String folderName = INBOX; // TODO should be an attribute in 'protocol' schema
rootFolder = store.getFolder(folderName);
// need RW access to update message flags
rootFolder.open(Folder.READ_WRITE);
Message[] allMessages = rootFolder.getMessages();
// VDU
log.debug("nbr of messages in folder:" + allMessages.length);
FetchProfile fetchProfile = new FetchProfile();
fetchProfile.add(FetchProfile.Item.FLAGS);
fetchProfile.add(FetchProfile.Item.ENVELOPE);
fetchProfile.add(FetchProfile.Item.CONTENT_INFO);
fetchProfile.add("Message-ID");
fetchProfile.add("Content-Transfer-Encoding");
rootFolder.fetch(allMessages, fetchProfile);
if (rootFolder instanceof IMAPFolder) {
// ((IMAPFolder)rootFolder).doCommand(FetchProfile)
}
List<Message> unreadMessagesList = new ArrayList<Message>();
for (Message message : allMessages) {
Flags flags = message.getFlags();
int unreadMessagesListSize = unreadMessagesList.size();
if (flags != null && !flags.contains(Flag.SEEN) && unreadMessagesListSize < emailsLimitLongValue) {
unreadMessagesList.add(message);
if (unreadMessagesListSize == emailsLimitLongValue - 1) {
break;
}
}
}
Message[] unreadMessagesArray = unreadMessagesList.toArray(new Message[unreadMessagesList.size()]);
// perform email import
visitor.visit(unreadMessagesArray, initialExecutionContext);
// perform flag update globally
Flags flags = new Flags();
flags.add(Flag.SEEN);
boolean leaveOnServer = (Boolean) initialExecutionContext.get(LEAVE_ON_SERVER_KEY);
if ((IMAP.equals(protocolType) || IMAPS.equals(protocolType)) && leaveOnServer) {
flags.add(Flag.SEEN);
} else {
flags.add(Flag.DELETED);
}
rootFolder.setFlags(unreadMessagesArray, flags, true);
} finally {
if (rootFolder != null && rootFolder.isOpen()) {
rootFolder.close(true);
}
if (store != null) {
store.close();
}
}
}
}
}