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 * Alexandre Russel 018 * 019 */ 020 021package org.nuxeo.ecm.platform.mail.service; 022 023import java.util.HashMap; 024import java.util.Map; 025import java.util.Properties; 026import java.util.concurrent.ConcurrentHashMap; 027import java.util.stream.Collectors; 028 029import javax.mail.Address; 030import javax.mail.Folder; 031import javax.mail.Message; 032import javax.mail.MessagingException; 033import javax.mail.Session; 034import javax.mail.Store; 035import javax.mail.Transport; 036import javax.mail.internet.MimeMessage; 037 038import org.nuxeo.ecm.platform.ec.notification.email.EmailHelper; 039import org.nuxeo.ecm.platform.mail.action.MailBoxActions; 040import org.nuxeo.ecm.platform.mail.action.MailBoxActionsImpl; 041import org.nuxeo.ecm.platform.mail.action.MessageActionPipe; 042import org.nuxeo.ecm.platform.mail.action.MessageActionPipeDescriptor; 043import org.nuxeo.ecm.platform.mail.fetcher.PropertiesFetcher; 044import org.nuxeo.ecm.platform.mail.fetcher.PropertiesFetcherDescriptor; 045import org.nuxeo.runtime.api.Framework; 046import org.nuxeo.runtime.model.ComponentContext; 047import org.nuxeo.runtime.model.ComponentInstance; 048import org.nuxeo.runtime.model.DefaultComponent; 049 050/** 051 * @author Alexandre Russel 052 */ 053public class MailServiceImpl extends DefaultComponent implements MailService { 054 055 private static final String SESSION_FACTORY = "sessionFactory"; 056 057 private static final String PROPERTIES_FETCHER = "propertiesFetcher"; 058 059 private static final String ACTION_PIPES = "actionPipes"; 060 061 /** 062 * Fetchers aggregated by name. 063 */ 064 private final Map<String, Class<? extends PropertiesFetcher>> fetchers = new HashMap<>(); 065 066 /** 067 * Session factories aggregated by name. 068 */ 069 private final Map<String, SessionFactoryDescriptor> sessionFactories = new HashMap<>(); 070 071 /** 072 * Fetchers aggregated by session factory name. 073 */ 074 private final Map<String, PropertiesFetcher> configuredFetchers = new HashMap<>(); 075 076 private final Map<String, MessageActionPipe> actionPipesRegistry = new HashMap<>(); 077 078 private final Map<String, MessageActionPipeDescriptor> actionPipeDescriptorsRegistry = new HashMap<>(); 079 080 protected final Map<String, Session> sessions = new ConcurrentHashMap<>(); 081 082 static { 083 setDecodeUTFFileNamesSystemProperty(); 084 } 085 086 @Override 087 public void stop(ComponentContext context) throws InterruptedException { 088 sessions.clear(); 089 } 090 091 @Override 092 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 093 if (extensionPoint.equals(SESSION_FACTORY)) { 094 SessionFactoryDescriptor descriptor = (SessionFactoryDescriptor) contribution; 095 registerSessionFactory(descriptor); 096 } else if (extensionPoint.equals(PROPERTIES_FETCHER)) { 097 PropertiesFetcherDescriptor descriptor = (PropertiesFetcherDescriptor) contribution; 098 fetchers.put(descriptor.getName(), descriptor.getFetcher()); 099 } else if (extensionPoint.equals(ACTION_PIPES)) { 100 MessageActionPipeDescriptor descriptor = (MessageActionPipeDescriptor) contribution; 101 registerActionPipe(descriptor); 102 } 103 } 104 105 @Override 106 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 107 // TODO deal with other extension points 108 if (extensionPoint.equals(ACTION_PIPES)) { 109 MessageActionPipeDescriptor descriptor = (MessageActionPipeDescriptor) contribution; 110 actionPipesRegistry.remove(descriptor.getName()); 111 } 112 } 113 114 private void registerSessionFactory(SessionFactoryDescriptor descriptor) { 115 sessionFactories.put(descriptor.getName(), descriptor); 116 } 117 118 private void registerActionPipe(MessageActionPipeDescriptor descriptor) { 119 if (!descriptor.getOverride()) { 120 MessageActionPipeDescriptor existingDescriptor = actionPipeDescriptorsRegistry.get(descriptor.getName()); 121 if (existingDescriptor != null) { 122 descriptor.merge(existingDescriptor); 123 } 124 } 125 actionPipeDescriptorsRegistry.put(descriptor.getName(), descriptor); 126 actionPipesRegistry.put(descriptor.getName(), descriptor.getPipe()); 127 } 128 129 private static void setDecodeUTFFileNamesSystemProperty() { 130 String toDecodeTheFilenames = Framework.getRuntime().getProperty("mail.mime.decodefilename"); 131 if (toDecodeTheFilenames == null) { 132 return; 133 } 134 toDecodeTheFilenames = toDecodeTheFilenames.trim().toLowerCase(); 135 if (toDecodeTheFilenames.equals("true") || toDecodeTheFilenames.equals("yes")) { 136 System.setProperty("mail.mime.decodefilename", "true"); 137 return; 138 } 139 System.setProperty("mail.mime.decodefilename", "false"); 140 } 141 142 public Store getConnectedStore(String name) throws MessagingException { 143 return getConnectedStore(name, null); 144 } 145 146 public Store getConnectedStore(String name, Map<String, Object> context) throws MessagingException { 147 Properties props = getProperties(name, context); 148 Session session = newSession(props); 149 Store store = session.getStore(); 150 store.connect(props.getProperty("user"), props.getProperty("password")); 151 return store; 152 } 153 154 private Properties getProperties(String name, Map<String, Object> map) { 155 return getFetcher(name).getProperties(map); 156 } 157 158 public Transport getConnectedTransport(String name) throws MessagingException { 159 return getConnectedTransport(name, null); 160 } 161 162 public Transport getConnectedTransport(String name, Map<String, Object> context) throws MessagingException { 163 Properties props = getProperties(name, context); 164 Session session = newSession(props); 165 Transport transport = session.getTransport(); 166 transport.connect(props.getProperty("user"), props.getProperty("password")); 167 return transport; 168 } 169 170 public Session getSession(String name) { 171 return getSession(name, null); 172 } 173 174 public Session getSession(String name, Map<String, Object> context) { 175 Properties props = getProperties(name, context); 176 return newSession(props); 177 } 178 179 public MailBoxActions getMailBoxActions(String factoryName, String folderName) throws MessagingException { 180 return getMailBoxActions(factoryName, folderName, null); 181 } 182 183 public MailBoxActions getMailBoxActions(String factoryName, String folderName, Map<String, Object> context) 184 throws MessagingException { 185 Store store = getConnectedStore(factoryName, context); 186 Folder folder = store.getFolder(folderName); 187 return new MailBoxActionsImpl(folder, true); 188 } 189 190 public void sendMail(String text, String subject, String factory, Address[] recipients) { 191 sendMail(text, subject, factory, recipients, null); 192 } 193 194 public void sendMail(String text, String subject, String factory, Address[] recipients, Map<String, Object> context) { 195 Session session = getSession(factory, context); 196 Message message = new MimeMessage(session); 197 try { 198 message.setFrom(); 199 message.setSubject(subject); 200 message.setRecipients(Message.RecipientType.TO, recipients); 201 message.setText(text); 202 Transport.send(message); 203 } catch (MessagingException e) { 204 throw new RuntimeException(e); 205 } 206 } 207 208 public PropertiesFetcher getFetcher(String name) { 209 PropertiesFetcher fetcher = configuredFetchers.get(name); 210 if (fetcher == null) { 211 String fetcherName = sessionFactories.get(name).getFetcherName(); 212 Class<? extends PropertiesFetcher> clazz = fetchers.get(fetcherName); 213 SessionFactoryDescriptor descriptor = sessionFactories.get(name); 214 try { 215 fetcher = clazz.newInstance(); 216 } catch (ReflectiveOperationException e) { 217 throw new RuntimeException(e); 218 } 219 fetcher.configureFetcher(descriptor.getProperties()); 220 configuredFetchers.put(name, fetcher); 221 } 222 return fetcher; 223 } 224 225 public MessageActionPipe getPipe(String name) { 226 return actionPipesRegistry.get(name); 227 } 228 229 protected Session newSession(Properties props) { 230 // build a key for sessions cache 231 String sessionKey = props.entrySet() 232 .stream() 233 .map(e -> e.getKey() + "#" + e.getValue()) 234 .sorted() 235 .collect(Collectors.joining("-", "{", "}")); 236 return sessions.computeIfAbsent(sessionKey, k -> EmailHelper.newSession(props)); 237 } 238}