package act.mail; /*- * #%L * ACT Framework * %% * Copyright (C) 2014 - 2017 ActFramework * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import act.Act; import act.app.ActionContext; import act.app.App; import act.event.ActEvent; import act.event.SystemEvent; import act.util.ActContext; import act.view.Template; import act.view.ViewManager; import org.osgl.$; import org.osgl.concurrent.ContextLocal; import org.osgl.http.H; import org.osgl.logging.LogManager; import org.osgl.logging.Logger; import org.osgl.storage.ISObject; import org.osgl.storage.impl.SObject; import org.osgl.util.C; import org.osgl.util.E; import org.osgl.util.S; import javax.mail.*; import javax.mail.internet.*; import java.io.File; import java.util.*; public class MailerContext extends ActContext.Base<MailerContext> { public static class InitEvent extends ActEvent<MailerContext> implements SystemEvent { public InitEvent(MailerContext source) { super(source); } } private static final Logger logger = LogManager.get(MailerContext.class); private H.Format fmt = H.Format.HTML; private InternetAddress from; private String subject; private List<InternetAddress> to = C.newList(); private List<InternetAddress> cc = C.newList(); private List<InternetAddress> bcc = C.newList(); private String confId; private List<ISObject> attachments = C.newList(); private String senderPath; // e.g. com.mycorp.myapp.mailer.AbcMailer.foo private static final ContextLocal<MailerContext> _local = $.contextLocal(); public MailerContext(App app, String confId) { super(app); this.confId = confId; _local.set(this); ActionContext actionContext = ActionContext.current(); if (null != actionContext) { locale(actionContext.locale()); } app.eventBus().triggerSync(new InitEvent(this)); } @Override protected void releaseResources() { super.releaseResources(); to.clear(); cc.clear(); bcc.clear(); _local.remove(); } public MailerContext configId(String id) { confId = id; return this; } public String senderPath() { return senderPath; } public MailerContext senderPath(String path) { senderPath = path; return this; } @Override public String methodPath() { return senderPath; } public MailerContext senderPath(String className, String methodName) { senderPath = S.concat(className, ".", methodName); return this; } public MailerConfig mailerConfig() { return app().mailerConfigManager().config(confId); } @Override public Set<String> paramKeys() { throw E.tbd(); } @Override public String paramVal(String key) { throw E.tbd(); } @Override public String[] paramVals(String key) { throw E.tbd(); } @Override public MailerContext accept(H.Format fmt) { E.NPE(fmt); this.fmt = fmt; return this; } @Override public H.Format accept() { return null != fmt ? fmt : mailerConfig().contentType(); } @Override public MailerContext templatePath(String templatePath) { return super.templatePath(templatePath); } @Override public <T> T renderArg(String name) { return super.renderArg(name); } @Override public MailerContext renderArg(String name, Object val) { return super.renderArg(name, val); } @Override public Map<String, Object> renderArgs() { return super.renderArgs(); } /** * Called by bytecode enhancer to set the name list of the render arguments that is update * by the enhancer * @param names the render argument names separated by "," * @return this AppContext */ public MailerContext __appRenderArgNames(String names) { return renderArg("__arg_names__", C.listOf(names.split(","))); } public List<String> __appRenderArgNames() { return renderArg("__arg_names__"); } public H.Format contentType() { return accept(); } public String subject() { return null != subject ? subject : mailerConfig().subject(); } public MailerContext subject(String subject, Object ... args) { if (app().config().i18nEnabled()) { this.subject = i18n(subject, args); } else { this.subject = S.fmt(subject, args); } return this; } public MailerContext attach(ISObject... sobjs) { attachments.addAll(C.listOf(sobjs)); return this; } public MailerContext attach(File... files) { for (File file : files) { attachments.add(SObject.of(file)); } return this; } public MailerContext from(String from) { E.illegalArgumentIf(S.empty(from), "<from> cannot be empty"); List<InternetAddress> l = canonicalRecipients(null, from); E.illegalArgumentIf(l.isEmpty(), "from address expected"); if (l.size() > 1) { logger.warn("There are more than one email address specified, only the first one will be used as From address"); } this.from = l.get(0); return this; } public InternetAddress from() { if (null == from) { return mailerConfig().from(); } return from; } /** * Set to recipients * * @param recipients the list of emails * @return this mailer context */ public MailerContext to(String... recipients) { to = canonicalRecipients(null, recipients); return this; } public List<InternetAddress> to() { return to.isEmpty() ? mailerConfig().to() : to; } public MailerContext cc(String... recipients) { cc = canonicalRecipients(null, recipients); return this; } public List<InternetAddress> cc() { return cc.isEmpty() ? mailerConfig().ccList() : cc; } public MailerContext bcc(String... recipients) { bcc = canonicalRecipients(null, recipients); return this; } public List<InternetAddress> bcc() { return bcc.isEmpty() ? mailerConfig().bccList() : bcc; } public MailerContext addTo(String... recipients) { canonicalRecipients(to, recipients); return this; } public MailerContext addCc(String... recipients) { canonicalRecipients(cc, recipients); return this; } public MailerContext addBcc(String... recipients) { canonicalRecipients(bcc, recipients); return this; } public boolean send() { try { MimeMessage message = createMessage(); if (!mailerConfig().mock()) { if (logger.isDebugEnabled()) { logger.debug("Sending email\n%sEnd email\n", debug(message)); } Transport.send(message); } else { logger.info("Sending email\n%sEnd email\n", debug(message)); } return true; } catch (Exception e) { logger.error(e, "Error sending email: %s", this); return false; } } private String debug(MimeMessage msg) throws Exception { List<String> lines = C.newList(); lines.add(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n>> recipients"); Address[] aa = msg.getAllRecipients(); if (null != aa) { for (Address a: aa) { lines.add(a.getType() + ":" + a.toString()); } } else { lines.add("[ERROR] no recipients defined"); } lines.add(">> header lines"); Enumeration e = msg.getAllHeaderLines(); while (e.hasMoreElements()) { lines.add(S.string(e.nextElement())); } Object content = msg.getContent(); if (content instanceof Multipart) { lines.add(">> multipart content"); Multipart mp = (Multipart)content; for (int i = 0; i < mp.getCount(); ++i) { MimeBodyPart bp = (MimeBodyPart)mp.getBodyPart(i); lines.add(S.fmt(">>> #%s [%s]", i, bp.getContentType())); String fileName = bp.getFileName(); if (S.notBlank(fileName)) { lines.add("file: " + fileName); } else { lines.add(S.string(bp.getContent())); } } } else { lines.add(">> content[" + msg.getContentType() + "]"); lines.add(S.string(msg.getContent())); } lines.add("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); return S.join("\n", lines); } private MimeMessage createMessage() throws Exception { MailerConfig config = mailerConfig(); if (null == config) { throw E.unexpected("Cannot find mailer config for %s", confId); } Session session = mailerConfig().session(); if (Act.isDev()) { session.setDebug(true); } MimeMessage msg = new MimeMessage(session); msg.setFrom(from()); msg.setSubject(subject()); msg.setSentDate(new Date()); msg.setRecipients(Message.RecipientType.TO, list2Array(to())); msg.setRecipients(Message.RecipientType.CC, list2Array(cc())); msg.setRecipients(Message.RecipientType.BCC, list2Array(bcc())); ViewManager vm = Act.viewManager(); Template t = vm.load(this); E.illegalStateIf(null == t, "Mail template not defined"); String content = t.render(this); if (attachments.isEmpty()) { msg.setText(content, config().encoding(), accept().name()); } else { Multipart mp = new MimeMultipart(); MimeBodyPart bp = new MimeBodyPart(); mp.addBodyPart(bp); bp.setText(content, config().encoding(), accept().name()); for (ISObject sobj : attachments) { MimeBodyPart attachment = new MimeBodyPart(); attachment.attachFile(sobj.asFile(), sobj.getAttribute(ISObject.ATTR_CONTENT_TYPE), "utf-8"); mp.addBodyPart(attachment); } msg.setContent(mp); } msg.saveChanges(); return msg; } private static InternetAddress[] list2Array(List<InternetAddress> list) { int len = list.size(); InternetAddress[] array = new InternetAddress[len]; return list.toArray(array); } private static final String SEP = "[;:,]+"; public static MailerContext current() { return _local.get(); } public static List<InternetAddress> canonicalRecipients(List<InternetAddress> l, String... recipients) { if (null == l) l = C.newList(); if (recipients.length == 0) return l; String s = S.join(",", recipients).replaceAll(SEP, ","); try { InternetAddress[] aa = InternetAddress.parse(s); l.addAll(C.listOf(aa)); return l; } catch (AddressException e) { throw E.unexpected(e); } } }