MailProvider.java
package com.wilzwert.myjobs.infrastructure.mail;
import com.wilzwert.myjobs.infrastructure.storage.SecureTempFileHelper;
import jakarta.annotation.PostConstruct;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.Multipart;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeBodyPart;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMultipart;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import java.io.*;
import java.nio.file.Files;
import java.util.Locale;
@Component
@Slf4j
public class MailProvider {
private final JavaMailSender mailSender;
private final TemplateEngine templateEngine;
/**
* The MessageSource used for translations
* This includes frontend URIs which could become localized in a near future
*/
private final MessageSource messageSource;
private final SecureTempFileHelper secureTempFileHelper;
private final String frontendUrl;
private final String from;
private final String fromName;
private final String defaultLanguage;
private File logoTempFile;
public MailProvider(
final JavaMailSender mailSender,
final TemplateEngine templateEngine,
final MessageSource messageSource,
final SecureTempFileHelper secureTempFileHelper,
final MailProperties mailProperties,
@Value("${application.frontend.url}") String frontendUrl,
@Value("${application.default-language}") String defaultLanguage
) {
this.mailSender = mailSender;
this.templateEngine = templateEngine;
this.messageSource = messageSource;
this.secureTempFileHelper = secureTempFileHelper;
this.frontendUrl = frontendUrl;
this.from = mailProperties.getFrom();
this.fromName = mailProperties.getFromName();
this.defaultLanguage = defaultLanguage;
}
@PostConstruct
public void init() throws IOException {
log.info("Copying logo to tmp file");
// copy images to tmp files at startup
ClassPathResource imgResource = new ClassPathResource("static/images/logo_email.png");
logoTempFile = Files.createTempFile("temp", ".png", secureTempFileHelper.getFileAttribute()).toFile();
if(!logoTempFile.exists()) {
throw new IOException("Could not find logo");
}
logoTempFile.deleteOnExit(); // cleanup on jvm exit
try (InputStream in = imgResource.getInputStream(); FileOutputStream out = new FileOutputStream(logoTempFile)) {
in.transferTo(out);
}
}
File getLogoTempFile() {
return logoTempFile;
}
public CustomMailMessage createMessage(String template, String recipientMail, String recipientName, String subject, String lang) {
return new CustomMailMessage(template, recipientMail, recipientName, subject, (lang != null ? lang : defaultLanguage));
}
/**
*
* Creates a URL based on the uri and the locale provided
* URI MUST be an entry in the MessageSource (i.e. resources/localization/messages[_lang].properties
* otherwise it will become unpredictable, should the frontend urls be localized
* Locale is used as is because it is based on the Lang enum,
* therefore we know its language tag will be either 'en' or 'fr'
* @param uri the uri
* @param locale the locale which will be used as uri prefix
* @return the complete url
*/
public String createUrl(String uri, Locale locale) {
String realUri = messageSource.getMessage(uri, null, locale);
return frontendUrl + "/" + locale.toLanguageTag() + "/" + realUri;
}
/**
*
* Creates a URL based on the uri, the locale provided, and some args
* Locale is used as is because it is based on the Lang enum,
* therefore we know its language tag will be either 'en' or 'fr'
*
* @param uri the uri as a th
* @param locale the locale which will be used as a uri prefix
* @return the complete url
*/
public String createUrl(String uri, Locale locale, Object... args) {
String realUri = messageSource.getMessage(uri, args, locale);
return frontendUrl + "/" + locale.toLanguageTag() + "/" + realUri;
}
/**
* Shortcut to generate /lang/me urls
* @param locale the locale to use to create the url
* @return the url to the user account
*/
public String createMeUrl(Locale locale) {
return createUrl("uri.me", locale);
}
private MimeMessage createMimeMessage(CustomMailMessage messageToSend, Locale locale, String htmlContent) throws MessagingException, UnsupportedEncodingException {
MimeMessage message = mailSender.createMimeMessage();
message.setFrom(new InternetAddress(from, fromName));
message.setRecipients(Message.RecipientType.TO, new InternetAddress(messageToSend.getRecipientMail(), messageToSend.getRecipientName()).toUnicodeString());
message.setSubject(messageSource.getMessage(messageToSend.getSubject(), null, locale), "UTF-8");
// send a multipart message to allow logo embedding
Multipart multipart = new MimeMultipart("related");
MimeBodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(htmlContent, "text/html; charset=UTF-8");
multipart.addBodyPart(htmlPart);
try {
MimeBodyPart imgPart = new MimeBodyPart();
imgPart.attachFile(logoTempFile);
imgPart.setContentID("<logoImage>");
multipart.addBodyPart(imgPart);
}
catch (IOException e) {
log.error("Unable to load logo_email.png", e);
}
message.setContent(multipart);
return message;
}
// TODO : improve exception handling with custom exceptions
@Async
public void send(CustomMailMessage messageToSend) {
try {
log.debug("MailProvider : begin sending message");
Context context = new Context(messageToSend.getLocale());
messageToSend.getVariables().forEach(context::setVariable);
String htmlContent = templateEngine.process(messageToSend.getTemplate(), context);
log.debug("Creating Mime Message to send");
MimeMessage mimeMessage = createMimeMessage(messageToSend, messageToSend.getLocale(), htmlContent);
log.debug("Passing the mimeMessage to the mail sender");
mailSender.send(mimeMessage);
log.debug("Mail should have been sent");
}
catch (Exception e) {
if(e instanceof MessagingException || e instanceof MailException) {
log.error("Unable to send message", e);
throw new MailSendException("Unable to send message", e);
}
log.error("Unexpected exception while building or sending the message", e);
throw new MailSendException("Unexpected exception while building or sending the message", e);
}
}
}