From ba648de666241784792e6a99bd0d6607f3825613 Mon Sep 17 00:00:00 2001
From: Lars Johansson <lars.johansson@ess.eu>
Date: Tue, 16 Nov 2021 16:22:52 +0100
Subject: [PATCH] NT-330: Send notification email once per batch request

---
 .../names/nameviews/TransactionBean.java      | 297 ++++++++++++++----
 .../names/util/NotificationService.java       |  45 +++
 2 files changed, 289 insertions(+), 53 deletions(-)

diff --git a/NamingConventionTool/src/main/java/org/openepics/names/nameviews/TransactionBean.java b/NamingConventionTool/src/main/java/org/openepics/names/nameviews/TransactionBean.java
index 172894a9..ec21f818 100644
--- a/NamingConventionTool/src/main/java/org/openepics/names/nameviews/TransactionBean.java
+++ b/NamingConventionTool/src/main/java/org/openepics/names/nameviews/TransactionBean.java
@@ -92,7 +92,9 @@ public class TransactionBean {
     private static final String PENDING = "pending";
     private static final String STATUS = "status";
 
-    private static final String DEFAULT_MAIL_SUBJECT = "There are new changes in Naming";
+    private static final String CHANGES_NEEDS_TO_BE_APPROVED = "[Changes - Needs to be approved]";
+    private static final String CHANGES_NOTIFICATION         = "[Changes - Notification]";
+    private static final String DEFAULT_MAIL_SUBJECT         = "There are new changes in Naming";
 
     private static final String MNEMONIC_IS_NOT_UNIQUE_ACCORDING_TO_NAMING_RULES =
             "Mnemonic is not unique according to naming rules.";
@@ -124,10 +126,51 @@ public class TransactionBean {
     @Inject
     private NamingHelper namingHelper;
 
+    // mail notification lists
+    //     keep track of MailNotificationDTO
+    //     keep track of cc per MailNotificationDTO
+    //     comparator for MailNotificationDTO to sort
+    private List<MailNotificationDTO> modificationSystemStructureApproval;
+    private List<MailNotificationDTO> modificationDeviceStructureApproval;
+    private List<MailNotificationDTO> modificationSystemStructureNonApproval;
+    private List<MailNotificationDTO> modificationDeviceStructureNonApproval;
+    private List<MailNotificationDTO> EMPTY_MODIFICATIONS = Collections.emptyList();
+    private HashMap<String, List<MailNotificationDTO>> ccModificationSystemStructure;
+    private HashMap<String, List<MailNotificationDTO>> ccModificationDeviceStructure;
+    private Comparator<MailNotificationDTO> comparatorMailNotificationDTO = new Comparator<MailNotificationDTO>() {
+        @Override
+        public int compare(MailNotificationDTO first, MailNotificationDTO second) {
+            List<String> list1 = first.getNodeList();
+            List<String> list2 = second.getNodeList();
+            if (list1.size() < list2.size()) {
+                return -1;
+            } else if (list2.size() < list1.size()) {
+                return 1;
+            } else {
+                for (int i=0; i<list1.size(); i++) {
+                    int val = list1.get(i).compareTo(list2.get(i));
+                    if (val != 0) {
+                        return val;
+                    } else {
+                        continue;
+                    }
+                }
+                return 0;
+            }
+        }
+    };
+
     private String nameViewStructure;
 
     @PostConstruct
     private void init() {
+        // mail notification lists
+        modificationSystemStructureApproval = new ArrayList<>();
+        modificationDeviceStructureApproval = new ArrayList<>();
+        modificationSystemStructureNonApproval = new ArrayList<>();
+        modificationDeviceStructureNonApproval = new ArrayList<>();
+        ccModificationSystemStructure = new HashMap<>();
+        ccModificationDeviceStructure = new HashMap<>();
     }
 
     /**
@@ -154,6 +197,9 @@ public class TransactionBean {
             em = emf.createEntityManager();
             Action action = new Action();
 
+            // clear mail notifications
+            clearNotifications();
+
             for (NameOperation operation : operations) {
                 if (operation instanceof Delete) {
                     action.execute((Delete) operation);
@@ -174,6 +220,12 @@ public class TransactionBean {
                 }
             }
 
+            // send mail notifications
+            sendNotifications();
+
+            // clear mail notifications
+            clearNotifications();
+
             transaction.commit();
             nameViewProvider.update(action.getRevisions());
 
@@ -859,19 +911,17 @@ public class TransactionBean {
 
                 String message = "Added -  Needs to be approved";
 
-                notificationService.notifyUserFromChange(
-                        Collections.singletonList(
-                                new MailNotificationDTO(
-                                        namingHelper.parentListForNode(newRevision, NamePartType.SECTION),
-                                        message,
-                                        operation.getMessage())),
-                        Collections.singletonList(
-                                new MailNotificationDTO(
-                                        namingHelper.parentListForNode(newRevision, NamePartType.DEVICE_TYPE),
-                                        message,
-                                        operation.getMessage())),
-                        Collections.singletonList(
-                                sessionService.user().getUsername()), "[Added - Needs to be approved]");
+                // notify users
+                addNotificationSystemStructureApproval(
+                        new MailNotificationDTO(
+                                namingHelper.parentListForNode(newRevision, NamePartType.SECTION),
+                                message,
+                                operation.getMessage()));
+                addNotificationDeviceStructureApproval(
+                        new MailNotificationDTO(
+                                namingHelper.parentListForNode(newRevision, NamePartType.DEVICE_TYPE),
+                                message,
+                                operation.getMessage()));
             }
         }
 
@@ -925,19 +975,16 @@ public class TransactionBean {
                 }
 
                 // notify users
-                notificationService.notifyUserFromChange(
-                        Collections.singletonList(
-                                new MailNotificationDTO(
-                                        namingHelper.parentListForNode(namePartRevision, NamePartType.SECTION),
-                                        message,
-                                        operation.getMessage())),
-                        Collections.singletonList(
-                                new MailNotificationDTO(
-                                        namingHelper.parentListForNode(namePartRevision, NamePartType.DEVICE_TYPE),
-                                        message,
-                                        operation.getMessage())),
-                        Collections.singletonList(
-                                sessionService.user().getUsername()), "[Modification - Needs to be approved]");
+                addNotificationSystemStructureApproval(
+                        new MailNotificationDTO(
+                                namingHelper.parentListForNode(namePartRevision, NamePartType.SECTION),
+                                message,
+                                operation.getMessage()));
+                addNotificationDeviceStructureApproval(
+                        new MailNotificationDTO(
+                                namingHelper.parentListForNode(namePartRevision, NamePartType.DEVICE_TYPE),
+                                message,
+                                operation.getMessage()));
             }
         }
 
@@ -1204,9 +1251,7 @@ public class TransactionBean {
      * @param subject the subject of the email
      */
     private void notifyAdminsFromChange(NameArtifact artifact, String operator, String subject, String commitMessage) {
-        List<MailNotificationDTO> modificationSystemStructure = null;
-        List<MailNotificationDTO> modificationDeviceStructure = null;
-        String requesterMail = null;
+        String requester = null;
 
         // get info for sending mail notification
         if (NameType.DEVICE_REGISTRY.equals(artifact.getNameType())) {
@@ -1232,34 +1277,180 @@ public class TransactionBean {
                             .setParameter(NAME_PART, artifact.asNamePart())
                             .getSingleResult();
 
-            requesterMail = namePartRevision.getRequestedBy().getUsername();
-
-            modificationSystemStructure =
-                    Collections.singletonList(
-                            new MailNotificationDTO(
-                                    namingHelper.parentListForNode(
-                                            namePartRevision, NamePartType.SECTION), operator, commitMessage));
-            modificationDeviceStructure =
-                    Collections.singletonList(
-                            new MailNotificationDTO(
-                                    namingHelper.parentListForNode(
-                                            namePartRevision, NamePartType.DEVICE_TYPE), operator, commitMessage));
+            requester = namePartRevision.getRequestedBy().getUsername();
+
+            MailNotificationDTO mailNotificationSystemStructure =
+                    NameType.SYSTEM_STRUCTURE.equals(artifact.getNameType())
+                        ? new MailNotificationDTO(
+                                namingHelper.parentListForNode(namePartRevision, NamePartType.SECTION),
+                                operator,
+                                commitMessage)
+                        : null;
+            MailNotificationDTO mailNotificationDeviceStructure =
+                    NameType.DEVICE_STRUCTURE.equals(artifact.getNameType())
+                        ? new MailNotificationDTO(
+                                namingHelper.parentListForNode(namePartRevision, NamePartType.DEVICE_TYPE),
+                                operator,
+                                commitMessage)
+                        : null;
+
+            if (mailNotificationSystemStructure != null) {
+                addNotificationSystemStructureNonApproval(mailNotificationSystemStructure);
+                addCCNotificationSystemStructure(requester, mailNotificationSystemStructure);
+            }
+            if (mailNotificationDeviceStructure != null) {
+                addNotificationDeviceStructureNonApproval(mailNotificationDeviceStructure);
+                addCCNotificationDeviceStructure(requester, mailNotificationDeviceStructure);
+            }
+        }
+    }
+
+    /**
+     * Clear (mail) notifications.
+     */
+    private void clearNotifications() {
+        // clear mail notifications
+        //     admin approval
+        //     admin non approval
+        //     cc
+
+        modificationSystemStructureApproval.clear();
+        modificationDeviceStructureApproval.clear();
+        modificationSystemStructureNonApproval.clear();
+        modificationDeviceStructureNonApproval.clear();
+        ccModificationSystemStructure.clear();
+        ccModificationDeviceStructure.clear();
+    }
+
+    /**
+     * Add (mail) notification for system structure (approval notification, admin).
+     *
+     * @param e notification
+     */
+    private void addNotificationSystemStructureApproval(MailNotificationDTO e) {
+        modificationSystemStructureApproval.add(e);
+    }
+
+    /**
+     * Add (mail) notification for device structure (approval notification, admin).
+     *
+     * @param e notification
+     */
+    private void addNotificationDeviceStructureApproval(MailNotificationDTO e) {
+        modificationDeviceStructureApproval.add(e);
+    }
+
+    /**
+     * Add (mail) notification for system structure (non-approval notification, admin).
+     *
+     * @param e notification
+     */
+    private void addNotificationSystemStructureNonApproval(MailNotificationDTO e) {
+        modificationSystemStructureNonApproval.add(e);
+    }
+
+    /**
+     * Add (mail) notification for device structure (non-approval notification, admin).
+     *
+     * @param e notification
+     */
+    private void addNotificationDeviceStructureNonApproval(MailNotificationDTO e) {
+        modificationDeviceStructureNonApproval.add(e);
+    }
+
+    /**
+     * Add (mail) notification for system structure (notification, non-admin).
+     *
+     * @param user user for cc
+     * @param e notification
+     */
+    private void addCCNotificationSystemStructure(String user, MailNotificationDTO e) {
+        List<MailNotificationDTO> ccList = ccModificationSystemStructure.get(user);
+        if (ccList == null) {
+            ccList = new ArrayList<>();
+        }
+
+        ccList.add(e);
+        ccModificationSystemStructure.put(user, ccList);
+    }
+
+    /**
+     * Add (mail) notification for device structure (notification, non-admin).
+     *
+     * @param user user for cc
+     * @param e notification
+     */
+    private void addCCNotificationDeviceStructure(String user, MailNotificationDTO e) {
+        List<MailNotificationDTO> ccList = ccModificationDeviceStructure.get(user);
+        if (ccList == null) {
+            ccList = new ArrayList<>();
+        }
+
+        ccList.add(e);
+        ccModificationDeviceStructure.put(user, ccList);
+    }
+
+    /**
+     * Send (mail) notifications.
+     */
+    private void sendNotifications() {
+        // send mail notificationss
+        //     sort and send
+        //     sort according to nodelist in MailNotificationDTO
+        //     cc
+        //         sessionService.user.getUsername (requester if approve, cancel, reject, delete
+        //     subject
+        //         [Changes - Needs to be approved]
+        //         [Changes - Notification]
+        //     send if conditions apply - content to send, non-admin cc
+
+        // sort
+        Collections.sort(modificationSystemStructureApproval, comparatorMailNotificationDTO);
+        Collections.sort(modificationDeviceStructureApproval, comparatorMailNotificationDTO);
+        Collections.sort(modificationSystemStructureNonApproval, comparatorMailNotificationDTO);
+        Collections.sort(modificationDeviceStructureNonApproval, comparatorMailNotificationDTO);
+
+        // send mail notification admin
+        if (!modificationSystemStructureApproval.isEmpty() || !modificationDeviceStructureApproval.isEmpty()) {
+            notificationService.notifyUserFromChange(
+                    modificationSystemStructureApproval,
+                    modificationDeviceStructureApproval,
+                    null,
+                    CHANGES_NEEDS_TO_BE_APPROVED);
         }
 
-        List<String> ccUsers = new ArrayList<>(Arrays.asList(sessionService.user().getUsername()));
+        // send mail notification cc admin
+        if (!modificationSystemStructureNonApproval.isEmpty() || !modificationDeviceStructureNonApproval.isEmpty()) {
+            notificationService.notifyUserFromChange(
+                    modificationSystemStructureNonApproval,
+                    modificationDeviceStructureNonApproval,
+                    null,
+                    CHANGES_NOTIFICATION);
+        }
 
-        //notify the requester user admin about decision
-        if(StringUtils.isNotEmpty(requesterMail)) {
-            ccUsers.add(requesterMail);
+        // send mail notification cc system structure if single non-admin cc
+        for (Map.Entry<String, List<MailNotificationDTO>> entry : ccModificationSystemStructure.entrySet()) {
+            Collections.sort(entry.getValue(), comparatorMailNotificationDTO);
+            if (!entry.getValue().isEmpty()) {
+                notificationService.notifyCCFromChange(
+                        entry.getValue(),
+                        EMPTY_MODIFICATIONS,
+                        Arrays.asList(entry.getKey()),
+                        CHANGES_NOTIFICATION);
+            }
         }
 
-        // notify users
-        // sending notification when "deleting" and entity
-        notificationService.notifyUserFromChange(
-                modificationSystemStructure,
-                modificationDeviceStructure,
-                ccUsers,
-                subject);
+        // send mail notification cc device structure if single non-admin cc
+        for (Map.Entry<String, List<MailNotificationDTO>> entry : ccModificationDeviceStructure.entrySet()) {
+            Collections.sort(entry.getValue(), comparatorMailNotificationDTO);
+            if (!entry.getValue().isEmpty()) {
+                notificationService.notifyCCFromChange(
+                        EMPTY_MODIFICATIONS,
+                        entry.getValue(),
+                        Arrays.asList(entry.getKey()),
+                        CHANGES_NOTIFICATION);
+            }
+        }
     }
 
 }
diff --git a/NamingConventionTool/src/main/java/org/openepics/names/util/NotificationService.java b/NamingConventionTool/src/main/java/org/openepics/names/util/NotificationService.java
index 8942e875..48b32f4b 100644
--- a/NamingConventionTool/src/main/java/org/openepics/names/util/NotificationService.java
+++ b/NamingConventionTool/src/main/java/org/openepics/names/util/NotificationService.java
@@ -110,6 +110,51 @@ public class NotificationService {
         return true;
     }
 
+    /**
+     * Sends email notification to cc if someone has made an operation
+     * on a node (system structure or device structure) or a leaf (device registry).
+     *
+     * @param modificationSystemStructure the system structure list that has been modified with an operator
+     * @param modificationDeviceStructure the device structure list that has been modified with an operator
+     * @param ccList the user that has applied the operator on a node/nodes
+     * @param subject subject for email notification
+     * @return was the mail sending successful, or not
+     */
+    public boolean notifyCCFromChange(List<MailNotificationDTO> modificationSystemStructure,
+                                      List<MailNotificationDTO> modificationDeviceStructure,
+                                      List<String> ccList,
+                                      String subject) {
+        // send email notifications as cc
+        //     no need to consider
+        //         getCCLoginListForNotification(SYSTEM_STRUCTURE_CHANGE)
+        //         getCCLoginListForNotification(DEVICE_STRUCTURE_CHANGE)
+        //     handled in notifyUserFromChange
+
+        LOGGER.log(Level.INFO, "Changes in Naming, trying to send email to cc if single non-admin cc");
+        if (ccList == null
+                || ccList.size() != 1
+                || userDirectoryService.getAllAdministratorEmails().contains(
+                        userDirectoryService.getEmail(ccList.get(0)))) {
+            LOGGER.log(Level.INFO, "Changes in Naming, not send email as not single non-admin cc");
+            return false;
+        }
+
+        try {
+            subject = subject + " - " + appBaseUrl;
+
+            mailService.sendMail(emailsForNames(ccList), emailsForNames(ccList), subject,
+                    generateChangeNotificationBody(
+                            modificationSystemStructure,
+                            modificationDeviceStructure,
+                             true),
+                    null, null, false);
+        } catch (Exception e) {
+            LOGGER.log(Level.SEVERE, "Error while trying to send changes-notification to cc", e);
+            return false;
+        }
+        return true;
+    }
+
     /**
      * Send batch-notification to all admins about devices that have been deleted
      *
-- 
GitLab