001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (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 *     npaslaru, tmartins, jcarsique
018 *
019 */
020
021package org.nuxeo.ecm.platform.ec.notification.email;
022
023import java.io.IOException;
024import java.io.Serializable;
025import java.io.StringReader;
026import java.io.StringWriter;
027import java.io.Writer;
028import java.util.Collection;
029import java.util.Date;
030import java.util.HashMap;
031import java.util.Map;
032import java.util.Properties;
033
034import javax.mail.Authenticator;
035import javax.mail.Message;
036import javax.mail.MessagingException;
037import javax.mail.Session;
038import javax.mail.Transport;
039import javax.mail.internet.InternetAddress;
040import javax.mail.internet.MimeMessage;
041import javax.naming.InitialContext;
042import javax.naming.NamingException;
043import javax.security.auth.login.LoginContext;
044import javax.security.auth.login.LoginException;
045
046import org.apache.commons.logging.Log;
047import org.apache.commons.logging.LogFactory;
048import org.mvel2.MVEL;
049import org.nuxeo.ecm.core.api.DocumentModel;
050import org.nuxeo.ecm.platform.ec.notification.NotificationConstants;
051import org.nuxeo.ecm.platform.ec.notification.service.NotificationService;
052import org.nuxeo.ecm.platform.ec.notification.service.NotificationServiceHelper;
053import org.nuxeo.ecm.platform.rendering.RenderingException;
054import org.nuxeo.ecm.platform.rendering.RenderingResult;
055import org.nuxeo.ecm.platform.rendering.RenderingService;
056import org.nuxeo.ecm.platform.rendering.impl.DocumentRenderingContext;
057import org.nuxeo.runtime.api.Framework;
058
059import freemarker.template.Configuration;
060import freemarker.template.Template;
061import freemarker.template.TemplateException;
062
063/**
064 * Class EmailHelper.
065 * <p>
066 * An email helper:
067 * <p>
068 *
069 * <pre>
070 * Hashtable mail = new Hashtable();
071 * mail.put(&quot;from&quot;, &quot;dion@almaer.com&quot;);
072 * mail.put(&quot;to&quot;, &quot;dion@almaer.com&quot;);
073 * mail.put(&quot;subject&quot;, &quot;a subject&quot;);
074 * mail.put(&quot;body&quot;, &quot;the body&quot;);
075 * &lt;p&gt;
076 * EmailHelper.sendmail(mail);
077 * </pre>
078 *
079 * Currently only supports one email in to address
080 */
081public class EmailHelper {
082
083    private static final Log log = LogFactory.getLog(EmailHelper.class);
084
085    // used for loading templates from strings
086    private final Configuration stringCfg = new Configuration();
087
088    protected static boolean javaMailNotAvailable = false;
089
090    /* Only static methods here chaps */
091    public EmailHelper() {
092    }
093
094    /**
095     * Static Method: sendmail(Map mail).
096     *
097     * @param mail A map of the settings
098     */
099    public void sendmail(Map<String, Object> mail) throws MessagingException {
100        try {
101            sendmail0(mail);
102        } catch (LoginException | IOException | TemplateException | RenderingException e) {
103            throw new MessagingException(e.getMessage(), e);
104        }
105    }
106
107    protected void sendmail0(Map<String, Object> mail) throws MessagingException, IOException, TemplateException,
108            LoginException, RenderingException {
109
110        Session session = getSession();
111        if (javaMailNotAvailable || session == null) {
112            log.warn("Not sending email since JavaMail is not configured");
113            return;
114        }
115
116        // Construct a MimeMessage
117        MimeMessage msg = new MimeMessage(session);
118        msg.setFrom(new InternetAddress(session.getProperty("mail.from")));
119        Object to = mail.get("mail.to");
120        if (!(to instanceof String)) {
121            log.error("Invalid email recipient: " + to);
122            return;
123        }
124        msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse((String) to, false));
125
126        RenderingService rs = Framework.getService(RenderingService.class);
127
128        DocumentRenderingContext context = new DocumentRenderingContext();
129        context.remove("doc");
130        context.putAll(mail);
131        context.setDocument((DocumentModel) mail.get("document"));
132        context.put("Runtime", Framework.getRuntime());
133
134        String customSubjectTemplate = (String) mail.get(NotificationConstants.SUBJECT_TEMPLATE_KEY);
135        if (customSubjectTemplate == null) {
136            String subjTemplate = (String) mail.get(NotificationConstants.SUBJECT_KEY);
137            Template templ = new Template("name", new StringReader(subjTemplate), stringCfg);
138
139            Writer out = new StringWriter();
140            templ.process(mail, out);
141            out.flush();
142
143            msg.setSubject(out.toString(), "UTF-8");
144        } else {
145            rs.registerEngine(new NotificationsRenderingEngine(customSubjectTemplate));
146
147            LoginContext lc = Framework.login();
148
149            Collection<RenderingResult> results = rs.process(context);
150            String subjectMail = "<HTML><P>No parsing Succeded !!!</P></HTML>";
151
152            for (RenderingResult result : results) {
153                subjectMail = (String) result.getOutcome();
154            }
155            subjectMail = NotificationServiceHelper.getNotificationService().getEMailSubjectPrefix() + subjectMail;
156            msg.setSubject(subjectMail, "UTF-8");
157
158            lc.logout();
159        }
160
161        msg.setSentDate(new Date());
162
163        rs.registerEngine(new NotificationsRenderingEngine((String) mail.get(NotificationConstants.TEMPLATE_KEY)));
164
165        LoginContext lc = Framework.login();
166
167        Collection<RenderingResult> results = rs.process(context);
168        String bodyMail = "<HTML><P>No parsing Succedeed !!!</P></HTML>";
169
170        for (RenderingResult result : results) {
171            bodyMail = (String) result.getOutcome();
172        }
173
174        lc.logout();
175
176        rs.unregisterEngine("ftl");
177
178        msg.setContent(bodyMail, "text/html; charset=utf-8");
179
180        // Send the message.
181        Transport.send(msg);
182    }
183
184    /**
185     * Gets the session from the JNDI.
186     */
187    private static Session getSession() {
188        Session session = null;
189        if (javaMailNotAvailable) {
190            return null;
191        }
192        // First, try to get the session from JNDI, as would be done under J2EE.
193        try {
194            NotificationService service = (NotificationService) Framework.getRuntime().getComponent(
195                    NotificationService.NAME);
196            InitialContext ic = new InitialContext();
197            session = (Session) ic.lookup(service.getMailSessionJndiName());
198        } catch (NamingException ex) {
199            log.warn("Unable to find Java mail API", ex);
200            javaMailNotAvailable = true;
201        }
202
203        return session;
204    }
205
206    /**
207     * Instantiate a new session that authenticate given the protocol's properties. Initialize also the default
208     * transport protocol handler according to the properties.
209     *
210     * @since 5.6
211     */
212    public static Session newSession(Properties props) {
213        Authenticator authenticator = new EmailAuthenticator(props);
214        Session session = Session.getDefaultInstance(props, authenticator);
215        String protocol = props.getProperty("mail.transport.protocol");
216        if (protocol != null && protocol.length() > 0) {
217            session.setProtocolForAddress("rfc822", protocol);
218        }
219        return session;
220    }
221
222    protected Map<String, Object> initMvelBindings(Map<String, Serializable> infos) {
223        Map<String, Object> map = new HashMap<String, Object>();
224        map.put("NotificationContext", infos);
225        return map;
226    }
227
228    /***
229     * Evaluates a MVEL expression within some context infos accessible on the "NotificationContext" object. Returns
230     * null if the result is not evaluated to a String
231     *
232     * @param expr
233     * @param ctx
234     * @return
235     * @since 5.6
236     */
237    public String evaluateMvelExpresssion(String expr, Map<String, Serializable> ctx) {
238        // check to see if there is a dynamic MVEL expr
239        Serializable compiledExpr = MVEL.compileExpression(expr);
240        Object result = MVEL.executeExpression(compiledExpr, initMvelBindings(ctx));
241        if (result instanceof String) {
242            return (String) result;
243        }
244        return null;
245    }
246}