fi.evolver.basics.spring.messaging.sender.SmtpSender Maven / Gradle / Ivy
package fi.evolver.basics.spring.messaging.sender;
import static fi.evolver.basics.spring.messaging.util.SendUtils.createDataStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import fi.evolver.basics.spring.common.ConfigurationRepository;
import fi.evolver.basics.spring.log.MessageLogService;
import fi.evolver.basics.spring.log.entity.MessageLog.Direction;
import fi.evolver.basics.spring.messaging.SendResult;
import fi.evolver.basics.spring.messaging.entity.Message;
import fi.evolver.basics.spring.messaging.util.SendUtils;
import fi.evolver.utils.OptionalUtils;
import fi.evolver.utils.UriUtils;
import jakarta.activation.DataHandler;
import jakarta.activation.DataSource;
import jakarta.mail.Authenticator;
import jakarta.mail.Message.RecipientType;
import jakarta.mail.MessagingException;
import jakarta.mail.PasswordAuthentication;
import jakarta.mail.Session;
import jakarta.mail.Transport;
import jakarta.mail.internet.AddressException;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeBodyPart;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMultipart;
import jakarta.mail.util.ByteArrayDataSource;
@Component
public class SmtpSender implements Sender {
private static final Logger LOG = LoggerFactory.getLogger(SmtpSender.class);
private static final String MAIL_SERVER_URI = "MailServerUri";
private static final String PROPERTY_ATTACHMENT_NAME = "AttachmentName";
private static final String PROPERTY_BODY = "Body";
private static final String PROPERTY_FROM = "From";
private static final String PROPERTY_REPLY_TO = "ReplyTo";
private static final String PROPERTY_SUBJECT = "Subject";
private static final InternetAddress DEFAULT_FROM;
static {
InternetAddress address = null;
try {
address = new InternetAddress("[email protected]");
}
catch (AddressException e) {
LOG.error("Could not initialize default from address");
throw new IllegalStateException(e);
}
DEFAULT_FROM = address;
}
private final ConfigurationRepository configurationRepository;
private final MessageLogService messageLogService;
@Autowired
public SmtpSender(ConfigurationRepository configurationRepository, MessageLogService messageLogService) {
this.configurationRepository = configurationRepository;
this.messageLogService = messageLogService;
}
@Override
public SendResult send(Message message, URI uri) {
try {
String subject = getSubject(message, uri);
List recipients = listRecipients(uri);
List attachments = createAttachments(message);
String mailBody = createMailBody(message, uri);
String from = message.getMessageTargetConfig().getProperty(PROPERTY_FROM).orElse(null);
List replyTo = message.getMessageTargetConfig().getProperty(PROPERTY_REPLY_TO).map(Collections::singletonList).orElse(null);
sendMail(message.getMessageType(), subject, mailBody, recipients, from, replyTo, attachments);
return SendResult.success();
}
catch (IOException e) {
LOG.warn("Failed sending mail", e);
return SendResult.error("Failed sending mail: %s", e.getMessage());
}
}
private static String createMailBody(Message message, URI uri) throws IOException {
String body = message.getMessageTargetConfig().getProperty(PROPERTY_BODY).orElse(getQueryParameter(uri, "body"));
if (body != null)
body = SendUtils.replaceTags(body, message);
if (body == null)
body = IOUtils.toString(new InputStreamReader(message.getDataStream(), StandardCharsets.UTF_8));
return body;
}
private static List listRecipients(URI uri) {
String schemeSpecificPart = uri.toString().replaceAll("^mailto:", "").replaceAll("\\?.*", "").replaceAll("#.*", "");
return Arrays.asList(schemeSpecificPart.split(","));
}
private static String getSubject(Message message, URI uri) throws IllegalArgumentException {
String subject = message.getMessageTargetConfig().getProperty(PROPERTY_SUBJECT)
.orElse(getQueryParameter(uri, "subject"));
if (subject == null)
throw new IllegalArgumentException("Required query parameter 'subject' not set");
return SendUtils.replaceTags(subject, message);
}
private static List createAttachments(Message message) throws IOException {
Optional name = message.getMessageTargetConfig().getProperty(PROPERTY_ATTACHMENT_NAME);
if (!name.isPresent())
return Collections.emptyList();
String attachmentName = SendUtils.replaceTags(name.get(), message);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try (InputStream in = createDataStream(message)) {
IOUtils.copy(in, bout);
}
return Collections.singletonList(new EmailAttachment(bout.toByteArray(), attachmentName));
}
private static String getQueryParameter(URI uri, String attributeName) {
String query = uri.toString().replaceAll("[^?]*(?:[?]([^#]*))?.*", "$1");
Pattern pattern = Pattern.compile("(?:^|&)" + attributeName + "=([^&]*)");
Matcher matcher = pattern.matcher(query);
if (matcher.find()) {
try {
return URLDecoder.decode(matcher.group(1), "UTF-8");
}
catch (UnsupportedEncodingException e) {
throw new IllegalStateException("BUG! UTF-8 not supported?", e);
}
}
return null;
}
public void sendMail(String messageType, String subject, String message, List recipients, String from, List replyTo, List emailAttachments) throws IOException {
LocalDateTime startTime = LocalDateTime.now();
String statusCode = "OK";
String statusMessage = "OK";
URI uri = null;
try {
uri = configurationRepository.getUri(MAIL_SERVER_URI)
.orElseThrow(() -> new IllegalStateException(MAIL_SERVER_URI + " not configured, "));
InternetAddress[] recipientAddresses = convert(recipients);
if (recipientAddresses.length == 0) {
LOG.warn("No recipients for mail, not sending");
statusMessage = "No recipients defined";
statusCode = "NO RECIPIENTS";
return;
}
InternetAddress[] replyToAddresses = convert(replyTo);
String user = UriUtils.getUserName(uri);
String password = UriUtils.getPassword(uri);
String host = uri.getHost();
int port = uri.getPort() == -1 ? 25 : uri.getPort();
InternetAddress fromAddress = inferFromAddress(from, uri);
boolean sslEnabled = UriUtils.getFragmentParameter(uri, "ssl").map(Boolean::parseBoolean).orElse(false);
boolean startTlsEnabled = UriUtils.getFragmentParameter(uri, "tls").map(Boolean::parseBoolean).orElse(false);
sendMail(host, port, user, password, sslEnabled, startTlsEnabled, subject, message, fromAddress, replyToAddresses, emailAttachments, recipientAddresses);
}
catch (MessagingException e) {
statusCode = "FAIL";
statusMessage = e.toString();
throw new IOException("Failed sending mail", e);
}
catch (RuntimeException e) {
statusCode = "FAIL";
statusMessage = e.toString();
throw e;
}
finally {
messageLogService.logMessage(
startTime,
messageType,
"smtp",
uri == null ? "?" : uri.toString(),
messageLogService.getApplicationName(),
uri == null ? "?" : uri.getHost(),
Direction.OUTBOUND,
message,
null,
null,
null,
statusCode,
statusMessage);
}
}
private static InternetAddress inferFromAddress(String from, URI uri) {
return OptionalUtils.stream(Optional.ofNullable(from), UriUtils.getFragmentParameter(uri, "from"))
.map(SmtpSender::convert)
.findFirst()
.orElse(DEFAULT_FROM);
}
private static InternetAddress convert(String address) {
if (address == null)
return null;
InternetAddress[] converted = convert(Collections.singletonList(address));
return converted.length >= 1 ? converted[0] : null;
}
private static InternetAddress[] convert(List addresses) {
if (addresses == null || addresses.isEmpty())
return new InternetAddress[0];
List results = new ArrayList<>(addresses.size());
for (String address: addresses) {
try {
results.add(new InternetAddress(address));
}
catch (AddressException e) {
LOG.warn("Invalid address");
}
}
return results.toArray(new InternetAddress[results.size()]);
}
private static void sendMail(
String host,
int port,
String user,
String password,
boolean sslEnabled,
boolean startTlsEnabled,
String subject,
String message,
InternetAddress from,
InternetAddress[] replyTo,
List emailAttachments,
InternetAddress... recipients) throws MessagingException {
Properties properties = System.getProperties();
properties.setProperty("mail.smtp.host", host);
properties.setProperty("mail.smtp.starttls.enable", Boolean.toString(startTlsEnabled));
if (user != null && user.length() > 0)
properties.setProperty("mail.smtp.user", user);
properties.setProperty("mail.smtp.port", String.valueOf(port));
if (password != null && password.length() > 0)
properties.setProperty("mail.smtp.auth", "true");
if (sslEnabled) {
properties.setProperty("mail.smtp.socketFactory.port", String.valueOf(port));
properties.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
properties.setProperty("mail.smtp.socketFactory.fallback", "false");
}
Session session = (password != null && password.length() > 0) ?
Session.getInstance(properties, new SimpleAuthenticator(user, password)) :
Session.getInstance(properties);
MimeMessage mail = new MimeMessage(session);
if (replyTo != null && replyTo.length > 0)
mail.setReplyTo(replyTo);
if (from != null)
mail.setFrom(from);
mail.setRecipients(RecipientType.TO, recipients);
mail.setSubject(subject);
MimeMultipart multipart = new MimeMultipart();
MimeBodyPart part = new MimeBodyPart();
multipart.addBodyPart(part);
part.setText(message, "utf-8");
part.setHeader("Content-Type","text/plain; charset=\"utf-8\"");
part.setHeader("Content-Transfer-Encoding", "quoted-printable");
for (EmailAttachment attachment : emailAttachments)
addAttachmentToEmail(attachment, multipart);
mail.setContent(multipart);
mail.setSentDate(new Date());
Transport.send(mail);
}
private static void addAttachmentToEmail(EmailAttachment emailAttachment, MimeMultipart multipart) {
try {
MimeBodyPart attachement = new MimeBodyPart();
DataSource source = new ByteArrayDataSource(emailAttachment.getData(), "APPLICATION/OCTET-STREAM");
attachement.setDataHandler(new DataHandler(source));
attachement.setFileName(emailAttachment.getFilename());
multipart.addBodyPart(attachement);
}
catch (Exception e) {
throw new IllegalArgumentException("FAILED to add attachment to email", e);
}
}
private static class SimpleAuthenticator extends Authenticator {
private final String user;
private final String password;
public SimpleAuthenticator(String user, String password) {
this.user = user;
this.password = password;
}
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, password);
}
}
private static class EmailAttachment {
private final byte[] data;
private final String filename;
public EmailAttachment(byte[] data, String filename) {
this.data = data;
this.filename = filename;
}
public byte[] getData() {
return data;
}
public String getFilename() {
return filename;
}
}
@Override
public Set getSupportedProtocols() {
return Set.of("mailto");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy