![JAR search and dependency download from the Maven repository](/logo.png)
net.yadaframework.components.YadaEmailService Maven / Gradle / Ivy
package net.yadaframework.components;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.MessageSource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.expression.ThymeleafEvaluationContext;
import jakarta.annotation.Resource;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import jakarta.servlet.http.HttpServletRequest;
import net.yadaframework.core.YadaConfiguration;
import net.yadaframework.core.YadaConstants;
import net.yadaframework.exceptions.YadaEmailException;
import net.yadaframework.exceptions.YadaInternalException;
import net.yadaframework.web.YadaEmailContent;
import net.yadaframework.web.YadaEmailParam;
@Service
// Deve stare in questo package perchè tirato dentro da YadaWebConfig, altrimenti SpringTemplateEngine non viene iniettato
public class YadaEmailService {
private Logger log = LoggerFactory.getLogger(YadaEmailService.class);
@Autowired private YadaConfiguration config;
@Autowired private JavaMailSender mailSender;
// Using @Resource we don't need a Qualifier annotation
@Resource
private SpringTemplateEngine emailTemplateEngine;
@Autowired private MessageSource messageSource;
// @Autowired private ServletContext servletContext;
@Autowired private ApplicationContext applicationContext;
@Autowired private YadaWebUtil yadaWebUtil;
// @Autowired private YadaUtil yadaUtil;
/**
* Convert a site-relative link to absolute, because in emails we can't use @{}.
* Example: th:href="${@yadaEmailService.buildLink('/read/234')}"
* @param relativeLink
* @return absolute link
*/
public String buildLink(String relativeLink) {
String myServerAddress = config.getServerAddress();
String relative = StringUtils.prependIfMissing(relativeLink, "/");
return myServerAddress + relative;
}
public boolean sendSupportRequest(String username, String supportRequest, HttpServletRequest request, Locale locale) {
final String emailName = "supportRequest";
final String[] toEmail = config.getSupportRequestRecipients();
final String[] subjectParams = {username};
String clientIp = yadaWebUtil.getClientIp(request);
String userAgent = request.getHeader("user-agent");
final Map templateParams = new HashMap();
templateParams.put("username", username);
templateParams.put("supportRequest", supportRequest);
templateParams.put("clientIp", clientIp);
templateParams.put("userAgent", userAgent);
Map inlineResources = new HashMap();
inlineResources.put("logosmall", config.getEmailLogoImage());
return sendHtmlEmail(toEmail, emailName, subjectParams, templateParams, inlineResources, locale, true);
}
/**
* @param toEmail
* @param emailName
* @param subjectParams
* @param templateParams
* @param inlineResources
* @param locale
* @param addTimestamp
* @deprecated use {@link YadaEmailBuilder} for a cleaner api
*/
@Deprecated
public boolean sendHtmlEmail(String[] toEmail, String emailName, Object[] subjectParams, Map templateParams, Map inlineResources, Locale locale, boolean addTimestamp) {
return sendHtmlEmail(toEmail, null, emailName, subjectParams, templateParams, inlineResources, locale, addTimestamp);
}
/**
* Invia una email usando un template thymeleaf.
* Il template è localizzato, per cui si può chiamare ad esempio .html oppure _de.html.
* Il subject è localizzato e parametrizzato, e la sua chiave è email.subject.
* @param toEmail
* @param replyTo can be null
* @param emailName nome del template, ovvero del file html senza estensione e localizzazione _xx
* @param subjectParams Valori opzionali da inserire nella stringa localizzata da usare come subject, può essere null
* @param templateParams variabili da usare nel template, come fossero attributi del Model. Può essere null.
* @param inlineResources mappa chiave-valore di immagini inline di tipo "cid:". Il valore è un path relativo al context-path, come per esempio "/res/img/pippo.jpg"
* @param locale
* @param addTimestamp true to add a timestamp to the subject
* @return true se l'email è stata spedita
* @deprecated use {@link YadaEmailBuilder} for a cleaner api
*/
@Deprecated
public boolean sendHtmlEmail(String[] toEmail, String replyTo, String emailName, Object[] subjectParams, Map templateParams, Map inlineResources, Locale locale, boolean addTimestamp) {
return sendHtmlEmail(config.getEmailFrom(), toEmail, replyTo, emailName, subjectParams, templateParams, inlineResources, null, locale, addTimestamp);
}
/**
*
* @param fromEmail
* @param toEmail
* @param replyTo
* @param emailName
* @param subjectParams
* @param templateParams
* @param inlineResources
* @param attachments
* @param locale
* @param addTimestamp
* @deprecated use {@link YadaEmailBuilder} for a cleaner api
*/
@Deprecated
public boolean sendHtmlEmail(String[] fromEmail, String[] toEmail, String replyTo, String emailName, Object[] subjectParams, Map templateParams, Map inlineResources, Map attachments, Locale locale, boolean addTimestamp) {
return sendHtmlEmail(fromEmail, toEmail, replyTo, emailName, subjectParams, templateParams, inlineResources, attachments, locale, addTimestamp, false);
}
/**
* Invia una email usando un template thymeleaf.
* Il template è localizzato, per cui si può chiamare ad esempio .html oppure _de.html.
* Il subject è localizzato e parametrizzato, e la sua chiave è email.subject.
* @param toEmail
* @param replyTo can be null
* @param emailName nome del template, ovvero del file html senza estensione e localizzazione _xx
* @param subjectParams Valori opzionali da inserire nella stringa localizzata da usare come subject, può essere null
* @param templateParams variabili da usare nel template, come fossero attributi del Model. Può essere null.
* @param inlineResources mappa chiave-valore di immagini inline di tipo "cid:". Il valore è un path relativo al context-path, come per esempio "/res/img/pippo.jpg"
* @param attachments mappa filename-File di file da inviare come attachment. Il filename deve avere la giusta estensione per avere il corretto mime type.
* @param locale
* @param addTimestamp true to add a timestamp to the subject
* @param batch true to send the separate email to each recipient
* @return true se l'email è stata spedita
* @deprecated use {@link YadaEmailBuilder} for a cleaner api
*/
@Deprecated
public boolean sendHtmlEmail(String[] fromEmail, String[] toEmail, String replyTo, String emailName, Object[] subjectParams, Map templateParams, Map inlineResources, Map attachments, Locale locale, boolean addTimestamp, boolean batch) {
YadaEmailParam yadaEmailParam = new YadaEmailParam();
yadaEmailParam.fromEmail = fromEmail;
yadaEmailParam.toEmail = toEmail;
yadaEmailParam.replyTo = replyTo;
yadaEmailParam.emailName = emailName;
yadaEmailParam.subjectParams = subjectParams;
yadaEmailParam.templateParams = templateParams;
yadaEmailParam.inlineResources = inlineResources;
yadaEmailParam.attachments = attachments;
yadaEmailParam.locale = locale;
yadaEmailParam.addTimestamp = addTimestamp;
return sendHtmlEmail(yadaEmailParam, batch);
}
/**
* Invia una email usando un template thymeleaf.
* Il template è localizzato, per cui si può chiamare ad esempio .html oppure _de.html.
* Il subject è localizzato e parametrizzato, e la sua chiave è email.subject.
* @return true se l'email è stata spedita
* @deprecated use {@link YadaEmailBuilder} for a cleaner api
*/
@Deprecated
public boolean sendHtmlEmail(YadaEmailParam yadaEmailParam) {
return this.sendHtmlEmail(yadaEmailParam, false);
}
/**
* Send a HTML email. Better use {@link YadaEmailBuilder} for a cleaner api
* @param yadaEmailParam
* @param batch
* @return true if the email has been sent correctly.
*/
public boolean sendHtmlEmail(YadaEmailParam yadaEmailParam, boolean batch) {
String[] fromEmail = yadaEmailParam.fromEmail;
String[] toEmail = yadaEmailParam.toEmail;
String replyTo = yadaEmailParam.replyTo;
String emailName = yadaEmailParam.emailName;
Object[] subjectParams = yadaEmailParam.subjectParams;
Map templateParams = yadaEmailParam.templateParams;
Map inlineResources = yadaEmailParam.inlineResources;
Map attachments = yadaEmailParam.attachments;
Locale locale = yadaEmailParam.locale;
boolean addTimestamp = yadaEmailParam.addTimestamp;
//
final String emailTemplate = getMailTemplateFile(emailName, locale);
final String subject = messageSource.getMessage("email.subject." + emailName, subjectParams, locale);
final String body = loadTemplate(locale, templateParams, emailTemplate);
YadaEmailContent ec = new YadaEmailContent();
ec.from = fromEmail!=null?fromEmail:config.getEmailFrom();
if (replyTo!=null) {
ec.replyTo = replyTo;
}
ec.to = toEmail;
ec.subject = subject + (addTimestamp?" (" + timestamp(locale) +")":"");
ec.body = body;
ec.html = true;
if (inlineResources!=null) {
ec.inlineResourceIds = new String[inlineResources.size()];
ec.inlineResources = new org.springframework.core.io.Resource[inlineResources.size()];
int i=0;
for (Entry entry : inlineResources.entrySet()) {
ec.inlineResourceIds[i] = entry.getKey();
// Must support fully qualified URLs, e.g. "file:C:/test.dat".
// Must support classpath pseudo-URLs, e.g. "classpath:test.dat".
// Should support relative file paths, e.g. "WEB-INF/test.dat". (This will be implementation-specific, typically provided by an ApplicationContext implementation.)
DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();
ec.inlineResources[i] = defaultResourceLoader.getResource(entry.getValue());
if (!ec.inlineResources[i].exists()) {
log.error("Invalid resource: " + entry.getValue());
}
// ec.inlineResources[i] = new ServletContextResourceLoader(servletContext).getResource(entry.getValue());
i++;
}
}
if (attachments!=null && attachments.size()>0) {
Set keySet = attachments.keySet();
int size = keySet.size();
ec.attachedFilenames = new String[size];
ec.attachedFiles = new File[size];
int i=0;
for (String filename : attachments.keySet()) {
ec.attachedFilenames[i] = filename;
ec.attachedFiles[i] = attachments.get(filename);
i++;
}
}
if (batch) {
List yadaEmailContents = new ArrayList<>();
for (String to : ec.to) {
yadaEmailContents.add(copyYadaEmailContent(ec, to));
}
return sendEmailBatch(yadaEmailContents);
} else {
return sendEmail(ec);
}
}
private YadaEmailContent copyYadaEmailContent(YadaEmailContent ec, String... to) {
YadaEmailContent newEc = new YadaEmailContent();
newEc.from = ec.from;
newEc.replyTo = ec.replyTo;
newEc.to = to;
newEc.cc = ec.cc;
newEc.bcc = ec.bcc;
newEc.subject = ec.subject;
newEc.body = ec.body;
newEc.html = ec.html;
newEc.inlineFiles = ec.inlineFiles;
newEc.inlineFileIds = ec.inlineFileIds;
newEc.inlineResources = ec.inlineResources;
newEc.inlineResourceIds = ec.inlineResourceIds;
newEc.attachedFiles = ec.attachedFiles;
newEc.attachedFilenames = ec.attachedFilenames;
return newEc;
}
public String loadTemplate(Locale locale, Map templateParams, String emailName) {
// String myServerAddress = yadaWebUtil.getWebappAddress(request);
// final WebContext ctx = new WebContext(request, response, servletContext, locale);
// Using Context instead of WebContext, we can't access WebContent files and can't use @{somelink}
final Context ctx = new Context(locale);
// This allows the use of @beans inside the email template
ctx.setVariable(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME, new ThymeleafEvaluationContext(applicationContext, null));
// Non so come si registra un bean resolver dentro a ctx, quindi uso "config" invece di "@config"
// TODO This "config" bean is deprecated and should be removed one day
ctx.setVariable("config", config);
//
if (templateParams !=null) {
for (Entry entry : templateParams.entrySet()) {
ctx.setVariable(entry.getKey(), entry.getValue());
}
}
// ctx.setVariable("beans", new Beans(applicationContext)); // So I can use "beans.myBean" in the template (workaround for the missing "@myBean" support)
final String body = this.emailTemplateEngine.process("/" + YadaConstants.EMAIL_TEMPLATES_FOLDER + "/" + emailName, ctx);
return body;
}
public String getTemplatePath(String templateName) {
return YadaConstants.EMAIL_TEMPLATES_PREFIX + "/" + YadaConstants.EMAIL_TEMPLATES_FOLDER + "/" + templateName;
}
/**
* Dato un nome di template senza estensione, ne ritorna il nome completo di localizzazione.
* Per esempio "saluti" diventa "saluti_it" se esiste, altrimenti resta "saluti".
* Se il template non esiste, lancia InternalException
* @param templateNameNoHtml
* @param locale
* @return
*/
public String getMailTemplateFile(String templateNameNoHtml, Locale locale) {
// String base = "/WEB-INF/emailTemplates/";
String prefix = templateNameNoHtml; // emailChange
String languagePart = "_" + locale.getLanguage(); // _it
String suffix = ".html";
String filename = prefix + languagePart + suffix; // emailChange_it.html
// TODO check if the / before filename is still needed
ClassPathResource classPathResource = new ClassPathResource(getTemplatePath(filename));
if (classPathResource.exists()) {
return prefix + languagePart;
}
filename = prefix + suffix; // emailChange.html
classPathResource = new ClassPathResource(getTemplatePath(filename));
if (classPathResource.exists()) {
return prefix;
}
throw new YadaInternalException("Email template not found: " + templateNameNoHtml);
}
/**
* Send an email by specifying the content directly, without a template
* @param from [address, name]
* @param to
* @param replyTo
* @param cc
* @param bcc
* @param subject
* @param body
* @param html
* @param inlineFile
* @param inlineFilename
* @return true if the email has been sent
*/
public boolean sendEmail(String[] from, String to, String replyTo, String cc, String bcc, String subject, String body, boolean html, File inlineFile, String inlineFilename) {
YadaEmailContent content = new YadaEmailContent();
content.from = from;
content.replyTo = replyTo;
content.to = new String[]{to};
content.cc = new String[]{cc};
content.bcc = new String[]{bcc};
content.subject = subject;
content.body = body;
content.html = html;
content.inlineFiles = new File[] {inlineFile};
content.inlineFileIds = new String[] {inlineFilename};
return sendEmail(content);
}
public boolean sendEmail(YadaEmailContent yadaEmailContent) {
try {
MimeMessage msg = createMimeMessage(yadaEmailContent);
if (msg!=null) {
log.debug("Sending email to '{}'...", Arrays.asList(yadaEmailContent.to));
mailSender.send(msg);
log.debug("Email sent to '{}'", Arrays.asList(yadaEmailContent.to));
return true;
}
} catch (Exception e) {
String to = ArrayUtils.toString(yadaEmailContent.to, "");
log.error("Error while sending email message to '{}'", to, e);
if (config.isEmailThrowExceptions()) {
throw new YadaEmailException(e);
}
}
return false;
}
public boolean sendEmailBatch(List yadaEmailContents) {
boolean result = true;
List messageList = new ArrayList();
for (YadaEmailContent yadaEmailContent : yadaEmailContents) {
try {
MimeMessage mimeMessage = createMimeMessage(yadaEmailContent);
if (mimeMessage!=null) {
messageList.add(mimeMessage);
}
} catch (Exception e) {
result = false;
log.error("Error while creating batch email message to {} (ignored)", yadaEmailContent.to, e);
}
}
if (messageList.size()>0) {
mailSender.send(messageList.toArray(new MimeMessage[messageList.size()])); // Batch
}
return result;
}
private String[] purifyRecipients(String[] addresses, YadaEmailContent yadaEmailContent) {
// Se esiste, mando solo se l'email è nella lista
List validEmail = config.getValidDestinationEmails();
if (validEmail!=null && !validEmail.isEmpty()) {
List recipients = new ArrayList(Arrays.asList(addresses)); // Il doppio passaggio serve perchè asList è fixed size
// Tengo solo quelli validi
if (recipients.retainAll(validEmail)) {
// Alcuni sono stati rimossi. Logghiamo quelli rimossi
List invalidEmails = new ArrayList(Arrays.asList(addresses)); // Il doppio passaggio serve perchè asList è fixed size
invalidEmails.removeAll(validEmail);
for (String address : invalidEmails) {
log.warn("Email not authorized in configuration (not in ). Skipping message for '{}' from='{}' subject='{}' body='{}'",
new Object[]{address, yadaEmailContent.from, yadaEmailContent.subject, yadaEmailContent.body});
}
}
return recipients.toArray(new String[0]);
} else {
return addresses;
}
}
private MimeMessage createMimeMessage(YadaEmailContent yadaEmailContent) throws MessagingException {
if (!config.isEmailEnabled()) {
log.warn("Emails not enabled. Skipping message from='{}' to='{}' cc='{}' bcc='{}' subject='{}' body='{}'",
new Object[]{yadaEmailContent.from, yadaEmailContent.to, yadaEmailContent.cc, yadaEmailContent.bcc, yadaEmailContent.subject, yadaEmailContent.body});
return null;
}
int totRecipients = 0;
if (yadaEmailContent.to!=null) {
yadaEmailContent.to = purifyRecipients(yadaEmailContent.to, yadaEmailContent);
totRecipients += yadaEmailContent.to.length;
}
if (yadaEmailContent.cc!=null) {
yadaEmailContent.cc = purifyRecipients(yadaEmailContent.cc, yadaEmailContent);
totRecipients += yadaEmailContent.cc.length;
}
if (yadaEmailContent.bcc!=null) {
yadaEmailContent.bcc = purifyRecipients(yadaEmailContent.bcc, yadaEmailContent);
totRecipients += yadaEmailContent.bcc.length;
}
if ( totRecipients == 0) {
return null;
}
if (!config.isProductionEnvironment()) {
String env = config.getApplicationEnvironment();
yadaEmailContent.subject = "[" + (StringUtils.isBlank(env)?"TEST":env.toUpperCase()) + "] " + yadaEmailContent.subject;
// emailContent.body = "Questa email è stata inviata durante un test del sistema, si prega di ignorarla, grazie.
" +
// "This email has been sent during a system test, please ignore it, thank you.
" + emailContent.body;
}
MimeMessage msg = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(msg, true, "UTF-8"); // true = multipart
try {
helper.setFrom(yadaEmailContent.from[0], yadaEmailContent.from[1]);
} catch (UnsupportedEncodingException e) {
log.error("Invalid encoding - ignoring 'from' personal name", e);
helper.setFrom(yadaEmailContent.from[0]);
}
if(yadaEmailContent.replyTo!=null) {
helper.setReplyTo(yadaEmailContent.replyTo);
}
if(yadaEmailContent.to!=null) {
helper.setTo(yadaEmailContent.to);
}
if(yadaEmailContent.cc!=null) {
helper.setCc(yadaEmailContent.cc);
}
if(yadaEmailContent.bcc!=null) {
helper.setBcc(yadaEmailContent.bcc);
}
helper.setSubject(yadaEmailContent.subject);
helper.setText(yadaEmailContent.body, yadaEmailContent.html); // true = html
if (yadaEmailContent.inlineFiles!=null && yadaEmailContent.inlineFileIds!=null && yadaEmailContent.inlineFiles.length==yadaEmailContent.inlineFileIds.length) {
for (int i = 0; i < yadaEmailContent.inlineFiles.length; i++) {
File file = yadaEmailContent.inlineFiles[i];
String fileId = yadaEmailContent.inlineFileIds[i];
helper.addInline(fileId, file);
}
}
if (yadaEmailContent.inlineResources!=null && yadaEmailContent.inlineResourceIds!=null && yadaEmailContent.inlineResources.length==yadaEmailContent.inlineResourceIds.length) {
for (int i = 0; i < yadaEmailContent.inlineResources.length; i++) {
org.springframework.core.io.Resource resource = yadaEmailContent.inlineResources[i];
String resourceId = yadaEmailContent.inlineResourceIds[i];
helper.addInline(resourceId, resource);
}
}
if (yadaEmailContent.attachedFiles!=null && yadaEmailContent.attachedFilenames!=null && yadaEmailContent.attachedFiles.length==yadaEmailContent.attachedFilenames.length) {
for (int i = 0; i < yadaEmailContent.attachedFiles.length; i++) {
File file = yadaEmailContent.attachedFiles[i];
String filename = yadaEmailContent.attachedFilenames[i];
helper.addAttachment(filename, file);
// helper.addInline(filename, file);
}
}
log.info("Sending email to={}, from={}, replyTo={}, cc={}, bcc={}, subject={}", new Object[] {yadaEmailContent.to, yadaEmailContent.from, yadaEmailContent.replyTo, yadaEmailContent.cc, yadaEmailContent.bcc, yadaEmailContent.subject});
log.debug("Email body = {}", yadaEmailContent.body);
return msg;
}
public String timestamp(Locale locale) {
return DateFormatUtils.format(new Date(), "yyyy-MM-dd@HH:mm", locale);
}
public String timestamp() {
return DateFormatUtils.ISO_DATETIME_FORMAT.format(new Date());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy