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 *     bstefanescu
018 */
019package org.nuxeo.ecm.automation.core.mail;
020
021import static org.nuxeo.mail.MailConstants.CONFIGURATION_JNDI_JAVA_MAIL;
022import static org.nuxeo.mail.MailConstants.DEFAULT_MAIL_JNDI_NAME;
023
024import java.io.File;
025import java.io.FileInputStream;
026import java.io.IOException;
027import java.io.StringReader;
028import java.io.StringWriter;
029import java.io.Writer;
030import java.net.URL;
031import java.util.List;
032import java.util.Properties;
033import java.util.concurrent.ConcurrentHashMap;
034import java.util.concurrent.ConcurrentMap;
035
036import javax.activation.DataHandler;
037import javax.mail.MessagingException;
038import javax.mail.internet.MimeBodyPart;
039import javax.mail.internet.MimeMultipart;
040import javax.validation.constraints.NotNull;
041
042import org.apache.commons.logging.Log;
043import org.apache.commons.logging.LogFactory;
044import org.nuxeo.ecm.core.api.Blob;
045import org.nuxeo.ecm.platform.rendering.api.RenderingException;
046import org.nuxeo.ecm.platform.rendering.api.ResourceLocator;
047import org.nuxeo.ecm.platform.rendering.fm.FreemarkerEngine;
048import org.nuxeo.runtime.api.Framework;
049
050import freemarker.core.Environment;
051import freemarker.template.Template;
052import freemarker.template.TemplateException;
053
054/**
055 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
056 */
057public class Composer {
058
059    private static final Log log = LogFactory.getLog(Composer.class);
060
061    /** Mail properties read from mail.properties. */
062    protected static final Properties MAIL_PROPERTIES = new Properties();
063
064    protected final FreemarkerEngine engine;
065
066    protected Mailer mailer;
067
068    // FIXME: don't put URLs in Maps, this is a serious performance issue.
069    protected final ConcurrentMap<String, URL> urls;
070
071    public Composer() {
072        this(null);
073    }
074
075    public Composer(Mailer mailer) {
076        urls = new ConcurrentHashMap<>();
077        if (mailer == null) {
078            this.mailer = createMailer();
079        } else {
080            this.mailer = mailer;
081        }
082        engine = new FreemarkerEngine();
083        engine.setResourceLocator(new ResourceLocator() {
084            @Override
085            public URL getResourceURL(String key) {
086                return urls.get(key);
087            }
088
089            @Override
090            public File getResourceFile(String key) {
091                return null;
092            }
093        });
094    }
095
096    protected Mailer createMailer() {
097        // first try the local configuration
098        // it was used by FakeSmtpMailServerFeature / EmbeddedAutomationClientTest#testSendMail - no runtime usage found
099        var properties = getMailProperties();
100        if (!properties.isEmpty()) {
101            mailer = new Mailer(properties);
102        }
103        // second try using JNDI
104        if (mailer == null) {
105            String name = Framework.getProperty(CONFIGURATION_JNDI_JAVA_MAIL, DEFAULT_MAIL_JNDI_NAME);
106            mailer = new Mailer(name);
107        }
108        return mailer;
109    }
110
111    @NotNull
112    protected static Properties getMailProperties() {
113        File mailFile = getMailPropertiesFile();
114        if ((mailFile != null && MAIL_PROPERTIES.isEmpty()) || Framework.isTestModeSet()) {
115            synchronized (MAIL_PROPERTIES) {
116                if ((mailFile != null && MAIL_PROPERTIES.isEmpty()) || Framework.isTestModeSet()) {
117                    MAIL_PROPERTIES.clear();
118                    if (mailFile != null) {
119                        try (FileInputStream in = new FileInputStream(mailFile)) {
120                            MAIL_PROPERTIES.load(in);
121                        } catch (IOException e) {
122                            log.error("Failed to load mail properties", e);
123                        }
124                    }
125                }
126            }
127        }
128        return MAIL_PROPERTIES;
129    }
130
131    protected static File getMailPropertiesFile() {
132        org.nuxeo.common.Environment env = org.nuxeo.common.Environment.getDefault();
133        if (env != null) {
134            File file = new File(env.getConfig(), "mail.properties");
135            if (file.isFile()) {
136                return file;
137            }
138        }
139        return null;
140    }
141
142    public void registerTemplate(URL url) {
143        urls.put(url.toExternalForm(), url);
144    }
145
146    public void unregisterTemplate(URL url) {
147        urls.remove(url.toExternalForm());
148    }
149
150    public void unregisterAllTemplates() {
151        urls.clear();
152    }
153
154    public Mailer getMailer() {
155        return mailer;
156    }
157
158    public FreemarkerEngine getEngine() {
159        return engine;
160    }
161
162    public void render(String template, Object ctx, Writer writer) throws RenderingException {
163        engine.render(template, ctx, writer);
164    }
165
166    public void render(URL template, Object ctx, Writer writer) throws RenderingException {
167        String key = template.toExternalForm();
168        urls.putIfAbsent(key, template);
169        engine.render(key, ctx, writer);
170    }
171
172    public String render(URL template, Object ctx) throws RenderingException {
173        String key = template.toExternalForm();
174        urls.putIfAbsent(key, template);
175        StringWriter writer = new StringWriter();
176        engine.render(key, ctx, writer);
177        return writer.toString();
178    }
179
180    public String render(String templateContent, Object ctx) throws TemplateException, IOException {
181        StringReader reader = new StringReader(templateContent);
182        Template temp = new Template("@inline", reader, engine.getConfiguration(), "UTF-8");
183        StringWriter writer = new StringWriter();
184        Environment env = temp.createProcessingEnvironment(ctx, writer, engine.getObjectWrapper());
185        env.process();
186        return writer.toString();
187    }
188
189    public Mailer.Message newMessage() {
190        return mailer.newMessage();
191    }
192
193    public Mailer.Message newTextMessage(URL template, Object ctx) throws RenderingException, MessagingException {
194        Mailer.Message msg = mailer.newMessage();
195        msg.setText(render(template, ctx), "UTF-8");
196        return msg;
197    }
198
199    public Mailer.Message newTextMessage(String templateContent, Object ctx) throws RenderingException,
200            MessagingException, TemplateException, IOException {
201        Mailer.Message msg = mailer.newMessage();
202        msg.setText(render(templateContent, ctx), "UTF-8");
203        return msg;
204    }
205
206    public Mailer.Message newHtmlMessage(URL template, Object ctx) throws RenderingException, MessagingException {
207        Mailer.Message msg = mailer.newMessage();
208        msg.setContent(render(template, ctx), "text/html; charset=utf-8");
209        return msg;
210    }
211
212    public Mailer.Message newHtmlMessage(String templateContent, Object ctx) throws MessagingException,
213            TemplateException, IOException {
214        Mailer.Message msg = mailer.newMessage();
215        msg.setContent(render(templateContent, ctx), "text/html; charset=utf-8");
216        return msg;
217    }
218
219    public Mailer.Message newMixedMessage(String templateContent, Object ctx, String textType, List<Blob> attachments)
220            throws TemplateException, IOException, MessagingException {
221        if (textType == null) {
222            textType = "plain";
223        }
224        Mailer.Message msg = mailer.newMessage();
225        MimeMultipart mp = new MimeMultipart();
226        MimeBodyPart body = new MimeBodyPart();
227        String result = render(templateContent, ctx);
228        body.setText(result, "UTF-8", textType);
229        mp.addBodyPart(body);
230        for (Blob blob : attachments) {
231            MimeBodyPart a = new MimeBodyPart();
232            a.setDataHandler(new DataHandler(new BlobDataSource(blob)));
233            a.setFileName(blob.getFilename());
234            mp.addBodyPart(a);
235        }
236        msg.setContent(mp);
237        return msg;
238    }
239
240}