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 *     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.Message;
035import javax.mail.MessagingException;
036import javax.mail.Session;
037import javax.mail.Transport;
038import javax.mail.internet.InternetAddress;
039import javax.mail.internet.MimeMessage;
040
041import org.apache.commons.logging.Log;
042import org.apache.commons.logging.LogFactory;
043import org.mvel2.MVEL;
044import org.nuxeo.ecm.core.api.DocumentModel;
045import org.nuxeo.ecm.core.api.NuxeoException;
046import org.nuxeo.ecm.platform.ec.notification.NotificationConstants;
047import org.nuxeo.ecm.platform.ec.notification.service.NotificationService;
048import org.nuxeo.ecm.platform.ec.notification.service.NotificationServiceHelper;
049import org.nuxeo.ecm.platform.notification.api.NotificationManager;
050import org.nuxeo.ecm.platform.rendering.RenderingException;
051import org.nuxeo.ecm.platform.rendering.RenderingResult;
052import org.nuxeo.ecm.platform.rendering.RenderingService;
053import org.nuxeo.ecm.platform.rendering.impl.DocumentRenderingContext;
054import org.nuxeo.mail.MailSessionBuilder;
055import org.nuxeo.runtime.api.Framework;
056import org.nuxeo.runtime.api.login.NuxeoLoginContext;
057
058import freemarker.template.Configuration;
059import freemarker.template.Template;
060import freemarker.template.TemplateException;
061
062/**
063 * Class EmailHelper.
064 * <p>
065 * An email helper:
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;template&quot;, &quot;a template name&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(Configuration.VERSION_2_3_0);
085
086    protected static boolean javaMailNotAvailable = false;
087
088    /**
089     * Static Method: sendmail(Map mail).
090     *
091     * @param mail A map of the settings
092     */
093    public void sendmail(Map<String, Object> mail) throws MessagingException {
094        try {
095            sendmail0(mail);
096        } catch (IOException | TemplateException | RenderingException e) {
097            throw new MessagingException(e.getMessage(), e);
098        }
099    }
100
101    protected void sendmail0(Map<String, Object> mail)
102            throws MessagingException, IOException, TemplateException, RenderingException {
103
104        Session session = getSession();
105        if (javaMailNotAvailable || session == null) {
106            log.warn("Not sending email since JavaMail is not configured");
107            return;
108        }
109
110        // Construct a MimeMessage
111        MimeMessage msg = new MimeMessage(session);
112        msg.setFrom(new InternetAddress(session.getProperty("mail.from")));
113        Object to = mail.get("mail.to");
114        if (!(to instanceof String)) {
115            log.error("Invalid email recipient: " + to);
116            return;
117        }
118        msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse((String) to, false));
119
120        RenderingService rs = Framework.getService(RenderingService.class);
121
122        DocumentRenderingContext context = new DocumentRenderingContext();
123        context.putAll(mail);
124        context.setDocument((DocumentModel) mail.get("document"));
125        context.put("Runtime", Framework.getRuntime());
126
127        String customSubjectTemplate = (String) mail.get(NotificationConstants.SUBJECT_TEMPLATE_KEY);
128        if (customSubjectTemplate == null) {
129            String subjTemplate = (String) mail.get(NotificationConstants.SUBJECT_KEY);
130            Template templ = new Template("name", new StringReader(subjTemplate), stringCfg);
131
132            Writer out = new StringWriter();
133            templ.process(mail, out);
134            out.flush();
135
136            msg.setSubject(out.toString(), "UTF-8");
137        } else {
138            rs.registerEngine(new NotificationsRenderingEngine(customSubjectTemplate));
139
140            try (NuxeoLoginContext loginContext = Framework.loginSystem()) {
141                Collection<RenderingResult> results = rs.process(context);
142                String subjectMail = "<HTML><P>No parsing Succeded !!!</P></HTML>";
143
144                for (RenderingResult result : results) {
145                    subjectMail = (String) result.getOutcome();
146                }
147                subjectMail = NotificationServiceHelper.getNotificationService().getEMailSubjectPrefix() + subjectMail;
148                msg.setSubject(subjectMail, "UTF-8");
149            }
150        }
151
152        msg.setSentDate(new Date());
153
154        String template = (String) mail.get(NotificationConstants.TEMPLATE_KEY);
155        rs.registerEngine(new NotificationsRenderingEngine(template));
156
157        String bodyMail = "<HTML><P>No parsing Succedeed !!!</P></HTML>";
158
159        Collection<RenderingResult> results;
160        try (NuxeoLoginContext lc = Framework.loginSystem()) {
161            results = rs.process(context);
162        }
163        for (RenderingResult result : results) {
164            bodyMail = (String) result.getOutcome();
165        }
166
167        rs.unregisterEngine(template);
168
169        msg.setContent(bodyMail, "text/html; charset=utf-8");
170
171        // Send the message.
172        Transport.send(msg);
173    }
174
175    /**
176     * Gets the session from the JNDI.
177     */
178    private static Session getSession() {
179        if (!javaMailNotAvailable) {
180            // First, try to get the session from JNDI, as would be done under J2EE.
181            try {
182                NotificationService service = (NotificationService) Framework.getService(NotificationManager.class);
183                return MailSessionBuilder.fromJndi(service.getMailSessionJndiName()).build();
184            } catch (NuxeoException ex) {
185                log.warn("Unable to find Java mail API", ex);
186                javaMailNotAvailable = true;
187            }
188        }
189        return null;
190    }
191
192    /**
193     * Instantiates a new session that authenticates given the protocol's properties. Initializes also the default
194     * transport protocol handler according to the properties.
195     *
196     * @since 5.6
197     * @deprecated since 11.1, use {@link MailSessionBuilder}
198     */
199    @Deprecated(since = "11.1")
200    public static Session newSession(Properties props) {
201        return MailSessionBuilder.fromProperties(props).build();
202    }
203
204    protected Map<String, Object> initMvelBindings(Map<String, Serializable> infos) {
205        Map<String, Object> map = new HashMap<>();
206        map.put("NotificationContext", infos);
207        return map;
208    }
209
210    /***
211     * Evaluates a MVEL expression within some context infos accessible on the "NotificationContext" object. Returns
212     * null if the result is not evaluated to a String
213     *
214     * @since 5.6
215     */
216    public String evaluateMvelExpresssion(String expr, Map<String, Serializable> ctx) {
217        // check to see if there is a dynamic MVEL expr
218        Serializable compiledExpr = MVEL.compileExpression(expr);
219        Object result = MVEL.executeExpression(compiledExpr, initMvelBindings(ctx));
220        if (result instanceof String) {
221            return (String) result;
222        }
223        return null;
224    }
225}