From ebc04b345cc6875f253ae98fda4ec8121188fb7d Mon Sep 17 00:00:00 2001 From: Lars Johansson <lars.johansson@ess.eu> Date: Wed, 4 Jan 2023 12:42:16 +0100 Subject: [PATCH] Add email capability --- pom.xml | 4 + .../configuration/MailConfiguration.java | 83 +++++++ .../openepics/names/service/MailService.java | 217 ++++++++++++++++++ src/main/resources/application.properties | 14 ++ src/test/resources/application.properties | 14 ++ 5 files changed, 332 insertions(+) create mode 100644 src/main/java/org/openepics/names/configuration/MailConfiguration.java create mode 100644 src/main/java/org/openepics/names/service/MailService.java diff --git a/pom.xml b/pom.xml index ddc357bf..757e9ef6 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,10 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-mail</artifactId> + </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> diff --git a/src/main/java/org/openepics/names/configuration/MailConfiguration.java b/src/main/java/org/openepics/names/configuration/MailConfiguration.java new file mode 100644 index 00000000..e7135bf1 --- /dev/null +++ b/src/main/java/org/openepics/names/configuration/MailConfiguration.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 European Spallation Source ERIC. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package org.openepics.names.configuration; + +import java.util.Properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +/** + * Set up email configuration for Naming backend. + * + * @author Lars Johansson + */ +@Configuration +public class MailConfiguration { + + @Value("${naming.smtp.host}") + String namingSmtpHost; + @Value("${naming.smtp.port}") + int namingSmtpPort; + @Value("${naming.smtp.username}") + String namingSmtpUsername; + @Value("${naming.smtp.password}") + String namingSmtpPassword; + + @Value("${spring.mail.properties.mail.smtp.auth}") + String springMailPropertiesMailSmtpAuth; + @Value("${spring.mail.properties.mail.smtp.starttls.enable}") + String springMailPropertiesMailSmtpStarttlsEnable; + @Value("${spring.mail.properties.mail.smtp.connectiontimeout}") + String springMailPropertiesMailSmtpConnectiontimeout; + @Value("${spring.mail.properties.mail.smtp.timeout}") + String springMailPropertiesMailSmtpTimeout; + @Value("${spring.mail.properties.mail.smtp.writetimeout}") + String springMailPropertiesMailSmtpWritetimeout; + @Value("${spring.mail.debug}") + String springMailDebug; + + /** + * Set up email configuration with sender. + */ + @Bean + public JavaMailSender getJavaMailSender() { + JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + + mailSender.setHost(namingSmtpHost); + mailSender.setPort(namingSmtpPort); + mailSender.setUsername(namingSmtpUsername); + mailSender.setPassword(namingSmtpPassword); + + Properties props = mailSender.getJavaMailProperties(); + props.put("mail.transport.protocol", "smtp"); + props.put("mail.smtp.auth", springMailPropertiesMailSmtpAuth); + props.put("mail.smtp.starttls.enable", springMailPropertiesMailSmtpStarttlsEnable); + props.put("mail.smtp.connectiontimeout", springMailPropertiesMailSmtpConnectiontimeout); + props.put("mail.smtp.timeout", springMailPropertiesMailSmtpTimeout); + props.put("mail.smtp.writetimeout", springMailPropertiesMailSmtpTimeout); + props.put("mail.debug", springMailDebug); + + return mailSender; + } + +} diff --git a/src/main/java/org/openepics/names/service/MailService.java b/src/main/java/org/openepics/names/service/MailService.java new file mode 100644 index 00000000..906196e8 --- /dev/null +++ b/src/main/java/org/openepics/names/service/MailService.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2023 European Spallation Source ERIC. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package org.openepics.names.service; + +import java.io.IOException; +import java.io.InputStream; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.activation.DataHandler; +import javax.activation.DataSource; +import javax.mail.Address; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +import javax.mail.util.ByteArrayDataSource; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +/** + * This class provides mail services. + * + * @author Lars Johansson + */ +@Service +public class MailService { + + private static final Logger LOGGER = Logger.getLogger(MailService.class.getName()); + + private static final String NAMING_PREFIX = "[NT] "; + private static final String SENDING_EMAIL_DISABLED = "Sending email disabled"; + private static final String SENDING_EMAIL_SUBJECT = "Sending email with subject {0}"; + + private static final String TO_EMAIL_ADDRESS_CAN_NOT_BE_USED = "To email address can not be used"; + private static final String CC_EMAIL_ADDRESS_CAN_NOT_BE_USED = "Cc email address can not be used"; + private static final String REPLY_TO_EMAIL_ADDRESS_CAN_NOT_BE_USED = "Reply to email address can not be used"; + + private static final String TO_EMAIL_ADDRESSES_UNVAVAILABLE_CAN_NOT_SEND_EMAIL = "To email addresses unavailable, can not send email"; + private static final String SUBJECT_UNAVAILABLE_CAN_NOT_SEND_EMAIL = "Subject unavailable, can not send email"; + private static final String CONTENT_UNAVAILABLE_CAN_NOT_SEND_EMAIL = "Content unavailable, can not send email"; + + private static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; + private static final String TEXT_HTML_CHARSET_UTF_8 = "text/html; charset=utf-8"; + + @Value("${naming.mail.notification}") + boolean namingMailNotification; + @Value("${naming.mail.from}") + String namingMailFrom; + + @Autowired + private JavaMailSender emailSender; + + @Autowired + private LogService logService; + + /** + * Send email. + * + * @param toEmailAddresses list of email addresses (to) + * @param ccEmailAddresses list of email addresses (cc) + * @param replyToEmailAddresses list of email addresses (reply-to) + * @param subject subject + * @param content content + * @param withAttachment if attachment is available (one or more) + * @param attachmentNames names of attachments + * @param attachments attachments + */ + public void sendEmail(String[] toEmailAddresses, String[] ccEmailAddresses, String[] replyToEmailAddresses, + String subject, String content, + boolean withAttachment, List<String> attachmentNames, List<InputStream> attachments) { + if (!namingMailNotification) { + LOGGER.log(Level.INFO, SENDING_EMAIL_DISABLED); + return; + } + + try { + // content + // check + // need to have + // to email address + // subject + // content + // handle + // to + // cc + // reply to + // from + // date + // subject + // content + // attachment + // send + + if (toEmailAddresses == null || toEmailAddresses.length == 0) { + LOGGER.log(Level.WARNING, TO_EMAIL_ADDRESSES_UNVAVAILABLE_CAN_NOT_SEND_EMAIL); + return; + } + if (StringUtils.isEmpty(subject)) { + LOGGER.log(Level.WARNING, SUBJECT_UNAVAILABLE_CAN_NOT_SEND_EMAIL); + return; + } + if (StringUtils.isEmpty(content)) { + LOGGER.log(Level.WARNING, CONTENT_UNAVAILABLE_CAN_NOT_SEND_EMAIL); + return; + } + + MimeMessage message = emailSender.createMimeMessage(); + + // to + // cc + // reply to + final List<Address> toRecipients = new ArrayList<>(); + for (final String toEmailAddress : toEmailAddresses) { + if (StringUtils.isEmpty(toEmailAddress)) { + LOGGER.log(Level.FINE, TO_EMAIL_ADDRESS_CAN_NOT_BE_USED); + continue; + } + toRecipients.add(new InternetAddress(toEmailAddress)); + } + if (toRecipients.isEmpty()) { + LOGGER.log(Level.WARNING, TO_EMAIL_ADDRESSES_UNVAVAILABLE_CAN_NOT_SEND_EMAIL); + return; + } + message.setRecipients(Message.RecipientType.TO, toRecipients.toArray(new Address[0])); + final List<Address> ccRecipients = new ArrayList<>(); + if (ccEmailAddresses != null) { + for (final String ccEmailAddress : ccEmailAddresses) { + if (StringUtils.isEmpty(ccEmailAddress)) { + LOGGER.log(Level.FINE, CC_EMAIL_ADDRESS_CAN_NOT_BE_USED); + continue; + } + ccRecipients.add(new InternetAddress(ccEmailAddress)); + } + } + if (!ccRecipients.isEmpty()) { + message.setRecipients(Message.RecipientType.CC, ccRecipients.toArray(new Address[0])); + } + final List<Address> replyToAddresses = new ArrayList<>(); + if (replyToEmailAddresses != null) { + for (final String replyToEmailAddress : replyToEmailAddresses) { + if (StringUtils.isEmpty(replyToEmailAddress)) { + LOGGER.log(Level.FINE, REPLY_TO_EMAIL_ADDRESS_CAN_NOT_BE_USED); + continue; + } + replyToAddresses.add(new InternetAddress(replyToEmailAddress)); + } + } + if (!replyToAddresses.isEmpty()) { + message.setReplyTo(replyToAddresses.toArray(new Address[0])); + } + + // from + // date + // subject + message.setSentDate(new Date()); + message.setFrom(new InternetAddress(namingMailFrom)); + message.setSubject(NAMING_PREFIX + subject); + + // attachment + // content + if (withAttachment + && attachmentNames != null && !attachmentNames.isEmpty() + && attachments != null && !attachments.isEmpty() + && attachmentNames.size() == attachments.size()) { + MimeMultipart multipart = new MimeMultipart(); + MimeBodyPart messagePart = new MimeBodyPart(); + messagePart.setContent(content, TEXT_HTML_CHARSET_UTF_8); + multipart.addBodyPart(messagePart); + for (int i = 0; i < attachments.size(); i++) { + MimeBodyPart attachmentPart = new MimeBodyPart(); + DataSource dataSource = new ByteArrayDataSource(attachments.get(i), APPLICATION_OCTET_STREAM); + attachmentPart.setDataHandler(new DataHandler(dataSource)); + attachmentPart.setFileName(attachmentNames.get(i)); + multipart.addBodyPart(attachmentPart); + } + message.setContent(multipart); + } else { + message.setContent(content, TEXT_HTML_CHARSET_UTF_8); + } + + LOGGER.log(Level.INFO, () -> + MessageFormat.format(SENDING_EMAIL_SUBJECT, + subject)); + emailSender.send(message); + } catch (MessagingException | IOException e) { + logService.logStackTraceElements(LOGGER, Level.WARNING, e); + } + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 62016c23..ab1cafdc 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -11,6 +11,20 @@ logging.level.org.openepics.names=INFO logging.level.org.springframework.web=INFO spring.http.log-request-details=true +# mail +naming.mail.notification=${NAMING_MAIL_NOTIFICATION:false} +naming.mail.from=${NAMING_MAIL_FROM:jboss@esss.se} +naming.smtp.host=${NAMING_SMTP_HOST:mail.esss.lu.se} +naming.smtp.port=${NAMING_SMTP_PORT:587} +naming.smtp.username=${NAMING_SMTP_USERNAME:} +naming.smtp.password=${NAMING_SMTP_PASSWORD:} +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true +spring.mail.properties.mail.smtp.connectiontimeout=5000 +spring.mail.properties.mail.smtp.timeout=3000 +spring.mail.properties.mail.smtp.writetimeout=5000 +spring.mail.debug=false + # spring # profiles # config diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index ca9f60fa..0498854c 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -11,6 +11,20 @@ logging.level.org.openepics.names=INFO logging.level.org.springframework.web=INFO spring.http.log-request-details=true +# mail +naming.mail.notification=${NAMING_MAIL_NOTIFICATION:false} +naming.mail.from=${NAMING_MAIL_FROM:jboss@esss.se} +naming.smtp.host=${NAMING_SMTP_HOST:mail.esss.lu.se} +naming.smtp.port=${NAMING_SMTP_PORT:587} +naming.smtp.username=${NAMING_SMTP_USERNAME:} +naming.smtp.password=${NAMING_SMTP_PASSWORD:} +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true +spring.mail.properties.mail.smtp.connectiontimeout=5000 +spring.mail.properties.mail.smtp.timeout=3000 +spring.mail.properties.mail.smtp.writetimeout=5000 +spring.mail.debug=false + # spring # profiles # config -- GitLab