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