001/* 002 * (C) Copyright 2006-2019 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 java.lang.Boolean.FALSE; 023import static java.lang.Boolean.TRUE; 024import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.CORE_SESSION_KEY; 025import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.EMAILS_LIMIT_PROPERTY_NAME; 026import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.EMAIL_PROPERTY_NAME; 027import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.HOST_PROPERTY_NAME; 028import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.IMAP; 029import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.IMAPS; 030import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.LEAVE_ON_SERVER_KEY; 031import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.MIMETYPE_SERVICE_KEY; 032import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PARENT_PATH_KEY; 033import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PASSWORD_PROPERTY_NAME; 034import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.POP3S; 035import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PORT_PROPERTY_NAME; 036import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PROTOCOL_TYPE_KEY; 037import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.PROTOCOL_TYPE_PROPERTY_NAME; 038import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.SOCKET_FACTORY_FALLBACK_PROPERTY_NAME; 039import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.SOCKET_FACTORY_PORT_PROPERTY_NAME; 040import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.SSL_PROTOCOLS_PROPERTY_NAME; 041import static org.nuxeo.ecm.platform.mail.utils.MailCoreConstants.STARTTLS_ENABLE_PROPERTY_NAME; 042import static org.nuxeo.mail.MailConstants.CONFIGURATION_MAIL_STORE_PROTOCOL; 043 044import java.util.ArrayList; 045import java.util.List; 046import java.util.Properties; 047import java.util.concurrent.CopyOnWriteArrayList; 048 049import javax.mail.FetchProfile; 050import javax.mail.Flags; 051import javax.mail.Flags.Flag; 052import javax.mail.Folder; 053import javax.mail.Message; 054import javax.mail.MessagingException; 055import javax.mail.Store; 056 057import org.apache.commons.lang3.StringUtils; 058import org.apache.commons.logging.Log; 059import org.apache.commons.logging.LogFactory; 060import org.nuxeo.ecm.core.api.CoreSession; 061import org.nuxeo.ecm.core.api.DocumentModel; 062import org.nuxeo.ecm.core.api.trash.TrashService; 063import org.nuxeo.ecm.platform.mail.action.ExecutionContext; 064import org.nuxeo.ecm.platform.mail.action.MessageActionPipe; 065import org.nuxeo.ecm.platform.mail.action.Visitor; 066import org.nuxeo.ecm.platform.mail.listener.MailEventListener; 067import org.nuxeo.ecm.platform.mail.service.MailService; 068import org.nuxeo.ecm.platform.mimetype.interfaces.MimetypeRegistry; 069import org.nuxeo.mail.MailSessionBuilder; 070import org.nuxeo.runtime.api.Framework; 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 /** 086 * @deprecated since 10.3, use {@link TrashService} instead 087 */ 088 @Deprecated 089 public static final String DELETED_LIFECYCLE_STATE = "deleted"; 090 091 public static final long EMAILS_LIMIT_DEFAULT = 100; 092 093 public static final String IMAP_DEBUG = "org.nuxeo.mail.imap.debug"; 094 095 protected static final CopyOnWriteArrayList<String> processingMailBoxes = new CopyOnWriteArrayList<>(); 096 097 private MailCoreHelper() { 098 } 099 100 /** 101 * Creates MailMessage documents for every unread mail found in the INBOX. The parameters needed to connect to the 102 * email INBOX are retrieved from the MailFolder document passed as a parameter. 103 */ 104 public static void checkMail(DocumentModel currentMailFolder, CoreSession coreSession) throws MessagingException { 105 106 if (processingMailBoxes.addIfAbsent(currentMailFolder.getId())) { 107 try { 108 doCheckMail(currentMailFolder, coreSession); 109 } finally { 110 processingMailBoxes.remove(currentMailFolder.getId()); 111 } 112 } else { 113 log.info("Mailbox " + currentMailFolder.getPathAsString() + " is already being processed"); 114 } 115 } 116 117 protected static void doCheckMail(DocumentModel currentMailFolder, CoreSession coreSession) 118 throws MessagingException { 119 String email = (String) currentMailFolder.getPropertyValue(EMAIL_PROPERTY_NAME); 120 String password = (String) currentMailFolder.getPropertyValue(PASSWORD_PROPERTY_NAME); 121 if (!StringUtils.isEmpty(email) && !StringUtils.isEmpty(password)) { 122 MailService mailService = Framework.getService(MailService.class); 123 124 MessageActionPipe pipe = mailService.getPipe(PIPE_NAME); 125 126 Visitor visitor = new Visitor(pipe); 127 Thread.currentThread().setContextClassLoader(Framework.class.getClassLoader()); 128 129 // initialize context 130 ExecutionContext initialExecutionContext = new ExecutionContext(); 131 132 initialExecutionContext.put(MIMETYPE_SERVICE_KEY, Framework.getService(MimetypeRegistry.class)); 133 134 initialExecutionContext.put(PARENT_PATH_KEY, currentMailFolder.getPathAsString()); 135 136 initialExecutionContext.put(CORE_SESSION_KEY, coreSession); 137 138 // TODO should be an attribute in 'protocol' schema 139 initialExecutionContext.put(LEAVE_ON_SERVER_KEY, TRUE); 140 141 Folder rootFolder = null; 142 Store store = null; 143 try { 144 String protocolType = (String) currentMailFolder.getPropertyValue(PROTOCOL_TYPE_PROPERTY_NAME); 145 initialExecutionContext.put(PROTOCOL_TYPE_KEY, protocolType); 146 147 String host = (String) currentMailFolder.getPropertyValue(HOST_PROPERTY_NAME); 148 String port = (String) currentMailFolder.getPropertyValue(PORT_PROPERTY_NAME); 149 Boolean socketFactoryFallback = (Boolean) currentMailFolder.getPropertyValue( 150 SOCKET_FACTORY_FALLBACK_PROPERTY_NAME); 151 String socketFactoryPort = (String) currentMailFolder.getPropertyValue( 152 SOCKET_FACTORY_PORT_PROPERTY_NAME); 153 Boolean starttlsEnable = (Boolean) currentMailFolder.getPropertyValue(STARTTLS_ENABLE_PROPERTY_NAME); 154 String sslProtocols = (String) currentMailFolder.getPropertyValue(SSL_PROTOCOLS_PROPERTY_NAME); 155 Long emailsLimit = (Long) currentMailFolder.getPropertyValue(EMAILS_LIMIT_PROPERTY_NAME); 156 long emailsLimitLongValue = emailsLimit == null ? EMAILS_LIMIT_DEFAULT : emailsLimit.longValue(); 157 158 String imapDebug = Framework.getProperty(IMAP_DEBUG, FALSE.toString()); 159 160 Properties properties = new Properties(); 161 properties.put(CONFIGURATION_MAIL_STORE_PROTOCOL, protocolType); 162 // Is IMAP connection 163 if (IMAP.equals(protocolType)) { 164 properties.put("mail.imap.host", host); 165 properties.put("mail.imap.port", port); 166 properties.put("mail.imap.starttls.enable", starttlsEnable.toString()); 167 properties.put("mail.imap.debug", imapDebug); 168 properties.put("mail.imap.partialfetch", FALSE.toString()); 169 } else if (IMAPS.equals(protocolType)) { 170 properties.put("mail.imaps.host", host); 171 properties.put("mail.imaps.port", port); 172 properties.put("mail.imaps.starttls.enable", starttlsEnable.toString()); 173 properties.put("mail.imaps.ssl.protocols", sslProtocols); 174 properties.put("mail.imap.partialfetch", FALSE.toString()); 175 properties.put("mail.imaps.partialfetch", FALSE.toString()); 176 } else if (POP3S.equals(protocolType)) { 177 properties.put("mail.pop3s.host", host); 178 properties.put("mail.pop3s.port", port); 179 properties.put("mail.pop3s.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); 180 properties.put("mail.pop3s.socketFactory.fallback", socketFactoryFallback.toString()); 181 properties.put("mail.pop3s.socketFactory.port", socketFactoryPort); 182 properties.put("mail.pop3s.ssl.protocols", sslProtocols); 183 } else { 184 // Is POP3 connection 185 properties.put("mail.pop3.host", host); 186 properties.put("mail.pop3.port", port); 187 } 188 189 properties.put("user", email); 190 properties.put("password", password); 191 192 store = MailSessionBuilder.fromProperties(properties).buildAndConnect(); 193 194 String folderName = INBOX; // TODO should be an attribute in 'protocol' schema 195 rootFolder = store.getFolder(folderName); 196 197 // need RW access to update message flags 198 rootFolder.open(Folder.READ_WRITE); 199 200 Message[] allMessages = rootFolder.getMessages(); 201 // VDU 202 log.debug("nbr of messages in folder:" + allMessages.length); 203 204 FetchProfile fetchProfile = new FetchProfile(); 205 fetchProfile.add(FetchProfile.Item.FLAGS); 206 fetchProfile.add(FetchProfile.Item.ENVELOPE); 207 fetchProfile.add(FetchProfile.Item.CONTENT_INFO); 208 fetchProfile.add("Message-ID"); 209 fetchProfile.add("Content-Transfer-Encoding"); 210 211 rootFolder.fetch(allMessages, fetchProfile); 212 213 List<Message> unreadMessagesList = new ArrayList<>(); 214 for (Message message : allMessages) { 215 Flags flags = message.getFlags(); 216 int unreadMessagesListSize = unreadMessagesList.size(); 217 if (flags != null && !flags.contains(Flag.SEEN) && unreadMessagesListSize < emailsLimitLongValue) { 218 unreadMessagesList.add(message); 219 if (unreadMessagesListSize == emailsLimitLongValue - 1) { 220 break; 221 } 222 } 223 } 224 225 Message[] unreadMessagesArray = unreadMessagesList.toArray(new Message[0]); 226 227 // perform email import 228 visitor.visit(unreadMessagesArray, initialExecutionContext); 229 230 // perform flag update globally 231 Flags flags = new Flags(); 232 flags.add(Flag.SEEN); 233 234 boolean leaveOnServer = (Boolean) initialExecutionContext.get(LEAVE_ON_SERVER_KEY); 235 if ((IMAP.equals(protocolType) || IMAPS.equals(protocolType)) && leaveOnServer) { 236 flags.add(Flag.SEEN); 237 } else { 238 flags.add(Flag.DELETED); 239 } 240 rootFolder.setFlags(unreadMessagesArray, flags, true); 241 242 } finally { 243 if (rootFolder != null && rootFolder.isOpen()) { 244 rootFolder.close(true); 245 } 246 if (store != null) { 247 store.close(); 248 } 249 } 250 } 251 } 252 253}