/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.mail;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.stream.Stream;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import divconq.bus.Message;
import divconq.hub.Hub;
import divconq.io.InputWrapper;
import divconq.io.OutputWrapper;
import divconq.lang.Memory;
import divconq.lang.op.OperationContext;
import divconq.lang.op.OperationResult;
import divconq.log.DebugLevel;
import divconq.struct.ListStruct;
import divconq.struct.RecordStruct;
import divconq.struct.Struct;
import divconq.util.HexUtil;
import divconq.util.StringUtil;
import divconq.work.IWork;
import divconq.work.TaskRun;
import divconq.xml.XElement;
public class SendWork implements IWork {
@Override
public void run(TaskRun task) {
XElement settings = MailTaskFactory.getSettings();
if (settings == null) {
task.error("Missing email settings");
task.complete();
return;
}
String smtpHost = settings.getAttribute("SmtpHost");
int smtpPort = (int) StringUtil.parseInt(settings.getAttribute("SmtpPort"), 587);
boolean smtpAuth = Struct.objectToBoolean(settings.getAttribute("SmtpAuth", "false"));
boolean smtpDebug = Struct.objectToBoolean(settings.getAttribute("SmtpDebug", "false"));
String smtpUsername = settings.getAttribute("SmtpUsername");
String smtpPassword = settings.hasAttribute("SmtpPassword")
? Hub.instance.getClock().getObfuscator().decryptHexToString(settings.getAttribute("SmtpPassword"))
: null;
String debugBCC = settings.getAttribute("BccDebug");
String skipto = settings.getAttribute("SkipToAddress");
RecordStruct req = (RecordStruct) task.getTask().getParams();
try {
String from = req.getFieldAsString("From");
String reply = req.getFieldAsString("ReplyTo");
if (StringUtil.isEmpty(from))
from = settings.getAttribute("DefaultFrom");
if (StringUtil.isEmpty(reply))
reply = settings.getAttribute("DefaultReplyTo");
String to = req.getFieldAsString("To");
String subject = req.getFieldAsString("Subject");
String body = req.getFieldAsString("Body");
String textbody = req.getFieldAsString("TextBody");
task.info(0, "Sending email from: " + from);
task.info(0, "Sending email to: " + to);
Properties props = new Properties();
if (smtpAuth) {
props.put("mail.smtp.auth", "true");
// TODO put this back in for Java8 - until then we have issues with Could not generate DH keypair
// see http://stackoverflow.com/questions/12743846/unable-to-send-an-email-using-smtp-getting-javax-mail-messagingexception-could
props.put("mail.smtp.starttls.enable", "true");
}
Session session = Session.getInstance(props);
// do debug on task with trace level
if (smtpDebug || (OperationContext.get().getLevel() == DebugLevel.Trace)) {
session.setDebugOut(new DebugPrintStream(task));
session.setDebug(true);
}
// Create a new Message
javax.mail.Message email = new MimeMessage(session);
InternetAddress fromaddr = StringUtil.isEmpty(from) ? null : InternetAddress.parse(from.replace(';', ','))[0];
InternetAddress[] rplyaddrs = StringUtil.isEmpty(reply) ? null : InternetAddress.parse(reply.replace(';', ','));
InternetAddress[] toaddrs = StringUtil.isEmpty(to) ? new InternetAddress[0] : InternetAddress.parse(to.replace(';', ','));
InternetAddress[] dbgaddrs = StringUtil.isEmpty(debugBCC) ? new InternetAddress[0] : InternetAddress.parse(debugBCC.replace(';', ','));
if (StringUtil.isNotEmpty(skipto)) {
List<InternetAddress> passed = new ArrayList<InternetAddress>();
for (int i = 0; i < toaddrs.length; i++) {
InternetAddress toa = toaddrs[i];
if (!toa.getAddress().contains(skipto))
passed.add(toa);
}
toaddrs = passed.stream().toArray(InternetAddress[]::new);
}
try {
email.setFrom(fromaddr);
if (rplyaddrs != null)
email.setReplyTo(rplyaddrs);
if (toaddrs != null)
email.addRecipients(javax.mail.Message.RecipientType.TO, toaddrs);
if (dbgaddrs != null)
email.addRecipients(javax.mail.Message.RecipientType.BCC, dbgaddrs);
email.setSubject(subject);
// ALTERNATIVE TEXT/HTML CONTENT
MimeMultipart cover = new MimeMultipart((textbody != null) ? "alternative" : "mixed");
if (textbody != null) {
MimeBodyPart txt = new MimeBodyPart();
txt.setText(textbody);
cover.addBodyPart(txt);
}
// add the message part
MimeBodyPart html = new MimeBodyPart();
html.setContent(body, "text/html");
cover.addBodyPart(html);
// add the attachment parts, if any
ListStruct attachments = req.getFieldAsList("Attachments");
if ((attachments != null) && (attachments.getSize() > 0)) {
// hints - https://mlyly.wordpress.com/2011/05/13/hello-world/
// COVER WRAP
MimeBodyPart wrap = new MimeBodyPart();
wrap.setContent(cover);
MimeMultipart content = new MimeMultipart("related");
content.addBodyPart(wrap);
for (Struct itm : attachments.getItems()) {
RecordStruct attachment = (RecordStruct) itm;
final String name = attachment.getFieldAsString("Name");
final String mime = attachment.getFieldAsString("Mime");
final Memory mem = attachment.getFieldAsBinary("Content");
mem.setPosition(0);
MimeBodyPart apart = new MimeBodyPart();
DataSource source = new DataSource() {
@Override
public OutputStream getOutputStream() throws IOException {
return new OutputWrapper(mem); // TODO technically we should reset mem to pos 0
}
@Override
public String getName() {
return name;
}
@Override
public InputStream getInputStream() throws IOException {
return new InputWrapper(mem); // TODO technically we should reset mem to pos 0
}
@Override
public String getContentType() {
return mime;
}
};
apart.setDataHandler(new DataHandler(source));
apart.setFileName(name);
content.addBodyPart(apart);
}
email.setContent(content);
}
else {
email.setContent(cover);
}
email.saveChanges();
}
catch (Exception x) {
task.error(1, "dciSendMail unable to send message due to invalid fields.");
}
InternetAddress[] recip = Stream.concat(Arrays.stream(toaddrs), Arrays.stream(dbgaddrs)).toArray(InternetAddress[]::new);
if (!task.hasErrors() && (recip.length > 0)) {
Transport t = null;
try {
t = session.getTransport("smtp");
t.connect(smtpHost, smtpPort, smtpUsername, smtpPassword);
t.sendMessage(email, recip);
t.close();
// TODO wish we could get INFO: Received successful response: 200, AWS Request ID: b599ca95-bc82-11e0-846a-ab5fa57d84d4
}
catch (Exception x) {
task.error(1, "dciSendMail unable to send message due to service problems. Error: " + x);
}
if (t != null) {
if (t.isConnected()) {
try {
t.close();
}
catch (MessagingException e) {
}
}
}
}
if (task.hasErrors())
task.info(0, "Unable to send email to: " + to);
else
task.info(0, "Email sent to: " + to);
}
catch (AddressException x) {
task.error(1, "dciSendMail unable to send message due to addressing problems. Error: " + x);
}
finally {
RecordStruct smsg = req.getFieldAsRecord("StatusMessage");
if (smsg != null) {
Message smsg2 = task.toLogMessage();
smsg2.copyFields(smsg);
Hub.instance.getBus().sendMessage(smsg2);
}
task.complete();
}
}
public class DebugPrintStream extends PrintStream {
protected OperationResult or = null;
public DebugPrintStream(OperationResult or) {
super(new OutputStream() {
@Override
public void write(int b) throws IOException {
if (b == 13)
System.out.println();
else
System.out.print(HexUtil.charToHex(b));
}
});
this.or = or;
}
@Override
public void println(String msg) {
or.trace(0, msg);
}
}
}