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