001/*
002 * (C) Copyright 2006-2009 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nuxeo - initial API and implementation
016 *
017 * $Id$
018 */
019
020package org.nuxeo.ecm.platform.mail.utils;
021
022import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.CORE_SESSION_KEY;
023import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.EMAIL_PROPERTY_NAME;
024import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.EMAILS_LIMIT_PROPERTY_NAME;
025import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.HOST_PROPERTY_NAME;
026import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.IMAP;
027import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.MIMETYPE_SERVICE_KEY;
028import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PARENT_PATH_KEY;
029import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PASSWORD_PROPERTY_NAME;
030import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PORT_PROPERTY_NAME;
031import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PROTOCOL_TYPE_PROPERTY_NAME;
032import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.SOCKET_FACTORY_FALLBACK_PROPERTY_NAME;
033import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.SOCKET_FACTORY_PORT_PROPERTY_NAME;
034import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.SSL_PROTOCOLS_PROPERTY_NAME;
035import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.STARTTLS_ENABLE_PROPERTY_NAME;
036import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.IMAPS;
037import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.POP3S;
038import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PROTOCOL_TYPE_KEY;
039import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.LEAVE_ON_SERVER_KEY;
040
041import java.util.ArrayList;
042import java.util.List;
043import java.util.Properties;
044import java.util.concurrent.CopyOnWriteArrayList;
045import java.util.concurrent.CopyOnWriteArraySet;
046
047import javax.mail.FetchProfile;
048import javax.mail.Flags;
049import javax.mail.Folder;
050import javax.mail.Message;
051import javax.mail.MessagingException;
052import javax.mail.Session;
053import javax.mail.Store;
054import javax.mail.Flags.Flag;
055
056import org.apache.commons.lang.StringUtils;
057import org.apache.commons.logging.Log;
058import org.apache.commons.logging.LogFactory;
059import org.nuxeo.ecm.core.api.CoreSession;
060import org.nuxeo.ecm.core.api.DocumentModel;
061import org.nuxeo.ecm.platform.mail.action.ExecutionContext;
062import org.nuxeo.ecm.platform.mail.action.MessageActionPipe;
063import org.nuxeo.ecm.platform.mail.action.Visitor;
064import org.nuxeo.ecm.platform.mail.service.MailService;
065import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry;
066import org.nuxeo.ecm.platform.mail.listener.MailEventListener;
067import org.nuxeo.runtime.api.Framework;
068
069import com.sun.mail.imap.IMAPFolder;
070import com.sun.mail.imap.IMAPMessage;
071
072/**
073 * Helper for Mail Core.
074 *
075 * @author Catalin Baican
076 */
077public final class MailCoreHelper {
078
079    private static final Log log = LogFactory.getLog(MailEventListener.class);
080
081    public static final String PIPE_NAME = "nxmail";
082
083    public static final String INBOX = "INBOX";
084
085    public static final String DELETED_LIFECYCLE_STATE = "deleted";
086
087    public static final long EMAILS_LIMIT_DEFAULT = 100;
088
089    private static MailService mailService;
090
091    private static MimetypeRegistry mimeService;
092
093    public static final String IMAP_DEBUG = "org.nuxeo.mail.imap.debug";
094
095    protected static final CopyOnWriteArrayList<String> processingMailBoxes = new CopyOnWriteArrayList<String>();
096
097    private MailCoreHelper() {
098    }
099
100    private static MailService getMailService() {
101        if (mailService == null) {
102            mailService = Framework.getService(MailService.class);
103        }
104        return mailService;
105    }
106
107    private static MimetypeRegistry getMimeService() {
108        if (mimeService == null) {
109            mimeService = Framework.getService(MimetypeRegistry.class);
110        }
111        return mimeService;
112    }
113
114    /**
115     * Creates MailMessage documents for every unread mail found in the INBOX. The parameters needed to connect to the
116     * email INBOX are retrieved from the MailFolder document passed as a parameter.
117     */
118    public static void checkMail(DocumentModel currentMailFolder, CoreSession coreSession) throws MessagingException {
119
120        if (processingMailBoxes.addIfAbsent(currentMailFolder.getId())) {
121            try {
122                doCheckMail(currentMailFolder, coreSession);
123            } finally {
124                processingMailBoxes.remove(currentMailFolder.getId());
125            }
126        } else {
127            log.info("Mailbox " + currentMailFolder.getPathAsString() + " is already being processed");
128        }
129    }
130
131    protected static void doCheckMail(DocumentModel currentMailFolder, CoreSession coreSession)
132            throws MessagingException {
133        String email = (String) currentMailFolder.getPropertyValue(EMAIL_PROPERTY_NAME);
134        String password = (String) currentMailFolder.getPropertyValue(PASSWORD_PROPERTY_NAME);
135        if (!StringUtils.isEmpty(email) && !StringUtils.isEmpty(password)) {
136            mailService = getMailService();
137
138            MessageActionPipe pipe = mailService.getPipe(PIPE_NAME);
139
140            Visitor visitor = new Visitor(pipe);
141            Thread.currentThread().setContextClassLoader(Framework.class.getClassLoader());
142
143            // initialize context
144            ExecutionContext initialExecutionContext = new ExecutionContext();
145
146            initialExecutionContext.put(MIMETYPE_SERVICE_KEY, getMimeService());
147
148            initialExecutionContext.put(PARENT_PATH_KEY, currentMailFolder.getPathAsString());
149
150            initialExecutionContext.put(CORE_SESSION_KEY, coreSession);
151
152            initialExecutionContext.put(LEAVE_ON_SERVER_KEY, Boolean.TRUE); // TODO should be an attribute in 'protocol'
153                                                                            // schema
154
155            Folder rootFolder = null;
156            Store store = null;
157            try {
158                String protocolType = (String) currentMailFolder.getPropertyValue(PROTOCOL_TYPE_PROPERTY_NAME);
159                initialExecutionContext.put(PROTOCOL_TYPE_KEY, protocolType);
160                // log.debug(PROTOCOL_TYPE_KEY + ": " + (String) initialExecutionContext.get(PROTOCOL_TYPE_KEY));
161
162                String host = (String) currentMailFolder.getPropertyValue(HOST_PROPERTY_NAME);
163                String port = (String) currentMailFolder.getPropertyValue(PORT_PROPERTY_NAME);
164                Boolean socketFactoryFallback = (Boolean) currentMailFolder.getPropertyValue(SOCKET_FACTORY_FALLBACK_PROPERTY_NAME);
165                String socketFactoryPort = (String) currentMailFolder.getPropertyValue(SOCKET_FACTORY_PORT_PROPERTY_NAME);
166                Boolean starttlsEnable = (Boolean) currentMailFolder.getPropertyValue(STARTTLS_ENABLE_PROPERTY_NAME);
167                String sslProtocols = (String) currentMailFolder.getPropertyValue(SSL_PROTOCOLS_PROPERTY_NAME);
168                Long emailsLimit = (Long) currentMailFolder.getPropertyValue(EMAILS_LIMIT_PROPERTY_NAME);
169                long emailsLimitLongValue = emailsLimit == null ? EMAILS_LIMIT_DEFAULT : emailsLimit.longValue();
170
171                String imapDebug = Framework.getProperty(IMAP_DEBUG, "false");
172
173                Properties properties = new Properties();
174                properties.put("mail.store.protocol", protocolType);
175                // properties.put("mail.host", host);
176                // Is IMAP connection
177                if (IMAP.equals(protocolType)) {
178                    properties.put("mail.imap.host", host);
179                    properties.put("mail.imap.port", port);
180                    properties.put("mail.imap.starttls.enable", starttlsEnable.toString());
181                    properties.put("mail.imap.debug", imapDebug);
182                    properties.put("mail.imap.partialfetch", "false");
183                } else if (IMAPS.equals(protocolType)) {
184                    properties.put("mail.imaps.host", host);
185                    properties.put("mail.imaps.port", port);
186                    properties.put("mail.imaps.starttls.enable", starttlsEnable.toString());
187                    properties.put("mail.imaps.ssl.protocols", sslProtocols);
188                    properties.put("mail.imaps.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
189                    properties.put("mail.imaps.socketFactory.fallback", socketFactoryFallback.toString());
190                    properties.put("mail.imaps.socketFactory.port", socketFactoryPort);
191                    properties.put("mail.imap.partialfetch", "false");
192                    properties.put("mail.imaps.partialfetch", "false");
193                } else if (POP3S.equals(protocolType)) {
194                    properties.put("mail.pop3s.host", host);
195                    properties.put("mail.pop3s.port", port);
196                    properties.put("mail.pop3s.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
197                    properties.put("mail.pop3s.socketFactory.fallback", socketFactoryFallback.toString());
198                    properties.put("mail.pop3s.socketFactory.port", socketFactoryPort);
199                    properties.put("mail.pop3s.ssl.protocols", sslProtocols);
200                } else {
201                    // Is POP3 connection
202                    properties.put("mail.pop3.host", host);
203                    properties.put("mail.pop3.port", port);
204                }
205
206                properties.put("user", email);
207                properties.put("password", password);
208
209                Session session = Session.getInstance(properties);
210
211                store = session.getStore();
212                store.connect(email, password);
213
214                String folderName = INBOX; // TODO should be an attribute in 'protocol' schema
215                rootFolder = store.getFolder(folderName);
216
217                // need RW access to update message flags
218                rootFolder.open(Folder.READ_WRITE);
219
220                Message[] allMessages = rootFolder.getMessages();
221                // VDU
222                log.debug("nbr of messages in folder:" + allMessages.length);
223
224                FetchProfile fetchProfile = new FetchProfile();
225                fetchProfile.add(FetchProfile.Item.FLAGS);
226                fetchProfile.add(FetchProfile.Item.ENVELOPE);
227                fetchProfile.add(FetchProfile.Item.CONTENT_INFO);
228                fetchProfile.add("Message-ID");
229                fetchProfile.add("Content-Transfer-Encoding");
230
231                rootFolder.fetch(allMessages, fetchProfile);
232
233                if (rootFolder instanceof IMAPFolder) {
234                    // ((IMAPFolder)rootFolder).doCommand(FetchProfile)
235                }
236
237                List<Message> unreadMessagesList = new ArrayList<Message>();
238                for (Message message : allMessages) {
239                    Flags flags = message.getFlags();
240                    int unreadMessagesListSize = unreadMessagesList.size();
241                    if (flags != null && !flags.contains(Flag.SEEN) && unreadMessagesListSize < emailsLimitLongValue) {
242                        unreadMessagesList.add(message);
243                        if (unreadMessagesListSize == emailsLimitLongValue - 1) {
244                            break;
245                        }
246                    }
247                }
248
249                Message[] unreadMessagesArray = unreadMessagesList.toArray(new Message[unreadMessagesList.size()]);
250
251                // perform email import
252                visitor.visit(unreadMessagesArray, initialExecutionContext);
253
254                // perform flag update globally
255                Flags flags = new Flags();
256                flags.add(Flag.SEEN);
257
258                boolean leaveOnServer = (Boolean) initialExecutionContext.get(LEAVE_ON_SERVER_KEY);
259                if ((IMAP.equals(protocolType) || IMAPS.equals(protocolType)) && leaveOnServer) {
260                    flags.add(Flag.SEEN);
261                } else {
262                    flags.add(Flag.DELETED);
263                }
264                rootFolder.setFlags(unreadMessagesArray, flags, true);
265
266            } finally {
267                if (rootFolder != null && rootFolder.isOpen()) {
268                    rootFolder.close(true);
269                }
270                if (store != null) {
271                    store.close();
272                }
273            }
274        }
275    }
276
277}