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}