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