diff --git a/pom.xml b/pom.xml index 757e9ef63b1b0d96b58d20e9a67d81b93553deda..ad95a1c7a9c4cb8db6751d5328442534ceefbc48 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-thymeleaf</artifactId> + </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> diff --git a/src/main/java/org/openepics/names/service/DeviceGroupService.java b/src/main/java/org/openepics/names/service/DeviceGroupService.java index aae236b4221508b55e3f389f5d03183d176603aa..0a334e1a5b6c14b6587cac00f490f60ea2ceb899 100644 --- a/src/main/java/org/openepics/names/service/DeviceGroupService.java +++ b/src/main/java/org/openepics/names/service/DeviceGroupService.java @@ -30,7 +30,6 @@ import org.openepics.names.repository.model.DeviceGroup; import org.openepics.names.repository.model.DeviceType; import org.openepics.names.rest.beans.Status; import org.openepics.names.rest.beans.Type; -import org.openepics.names.rest.beans.element.StructureElement; import org.openepics.names.rest.beans.element.StructureElementCommand; import org.openepics.names.util.HolderSystemDeviceStructure; import org.openepics.names.util.StructureChoice; @@ -38,6 +37,7 @@ import org.openepics.names.util.StructureCommand; import org.openepics.names.util.StructureElementUtil; import org.openepics.names.util.StructureUtil; import org.openepics.names.util.ValidateUtil; +import org.openepics.names.util.notification.NotificationUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; @@ -78,16 +78,16 @@ public class DeviceGroupService { } @Transactional(propagation = Propagation.MANDATORY) - public StructureElement deleteStructure(StructureElementCommand structureElement, - Date requested, String requestedBy) { + public StructureElementNotification deleteStructure(StructureElementCommand structureElement, + Date requested, String requestedBy, + HolderSystemDeviceStructure holder) { // validation outside method // transaction // do // find // create structure to pending, not latest, deleted, with data - // possibly validate that deleted // return - // structure element for deleted structure + // structure element for deleted structure + notification String uuid = structureElement.getUuid().toString(); @@ -105,11 +105,13 @@ public class DeviceGroupService { requested, requestedBy, structureElement.getComment()); deviceGroupRepository.createDeviceGroup(deviceGroup); - return StructureElementUtil.getStructureElementRequested(deviceGroup); + return new StructureElementNotification( + StructureElementUtil.getStructureElementRequested(deviceGroup), + NotificationUtil.prepareNotification(Type.DEVICEGROUP, StructureCommand.DELETE, null, null, deviceGroup, holder)); } @Transactional(propagation = Propagation.MANDATORY) - public StructureElement approveStructure(StructureElementCommand structureElement, + public StructureElementNotification approveStructure(StructureElementCommand structureElement, Date processed, String processedBy, HolderSystemDeviceStructure holder) { // validation outside method @@ -119,7 +121,6 @@ public class DeviceGroupService { // update not latest // find // approve - update structure to status APPROVED, latest to true - // possibly validate that approved // additional // find out previous to find out what is approved - create update delete // approve create @@ -136,7 +137,7 @@ public class DeviceGroupService { // keep related names - legacy names // delete sub structures // return - // structure element for approved structure + // structure element for approved structure + notification String uuid = structureElement.getUuid().toString(); String processedComment = structureElement.getComment(); @@ -164,13 +165,13 @@ public class DeviceGroupService { deviceGroup.setLatest(Boolean.TRUE); deviceGroupRepository.updateDeviceGroup(deviceGroup); - // additional - // find out previous + // previous List<DeviceGroup> previouses = iDeviceGroupRepository.findPreviousByUuidAndId(uuid, deviceGroup.getId()); StructureCommand structureCommandCUD = StructureUtil.getStructureCommandCUD(previouses, deviceGroup); + DeviceGroup previous = previouses != null && !previouses.isEmpty() ? previouses.get(0) : null; LOGGER.log(Level.FINE, "approveStructure, structureCommandCUD: {0}", structureCommandCUD); - // approve delete + // additional if (StructureCommand.DELETE.equals(structureCommandCUD)) { // not delete names - legacy names // delete sub structures - delete, approve @@ -180,7 +181,7 @@ public class DeviceGroupService { structureElementCommands.add(new StructureElementCommand(deviceType.getUuid(), Type.DEVICETYPE, null, null, null, null, StructuresService.DELETE_AFTER_APPROVE_STRUCTURE_CHANGE)); } for (StructureElementCommand structureElementCommand : structureElementCommands) { - deviceTypeService.deleteStructure(structureElementCommand, processed, processedBy); + deviceTypeService.deleteStructure(structureElementCommand, processed, processedBy, holder); } for (StructureElementCommand structureElementCommand : structureElementCommands) { deviceTypeService.approveStructure(structureElementCommand, processed, processedBy, holder); @@ -188,7 +189,9 @@ public class DeviceGroupService { } LOGGER.log(Level.FINE, "approveStructure, structureElement: {0}", structureElement); - return StructureElementUtil.getStructureElementProcessed(deviceGroup, holder, StructureChoice.STRUCTURE); + return new StructureElementNotification( + StructureElementUtil.getStructureElementProcessed(deviceGroup, holder, StructureChoice.STRUCTURE), + NotificationUtil.prepareNotification(Type.DEVICEGROUP, structureCommandCUD, StructureCommand.APPROVE, previous, deviceGroup, holder)); } } diff --git a/src/main/java/org/openepics/names/service/DeviceTypeService.java b/src/main/java/org/openepics/names/service/DeviceTypeService.java index 10cab786a79884d4e23f701d4b28d20a2e84365f..ff73eba5e79c2f97ce5b540122aee505eb496291 100644 --- a/src/main/java/org/openepics/names/service/DeviceTypeService.java +++ b/src/main/java/org/openepics/names/service/DeviceTypeService.java @@ -27,7 +27,7 @@ import org.openepics.names.repository.DeviceTypeRepository; import org.openepics.names.repository.IDeviceTypeRepository; import org.openepics.names.repository.model.DeviceType; import org.openepics.names.rest.beans.Status; -import org.openepics.names.rest.beans.element.StructureElement; +import org.openepics.names.rest.beans.Type; import org.openepics.names.rest.beans.element.StructureElementCommand; import org.openepics.names.util.HolderSystemDeviceStructure; import org.openepics.names.util.StructureChoice; @@ -35,6 +35,7 @@ import org.openepics.names.util.StructureCommand; import org.openepics.names.util.StructureElementUtil; import org.openepics.names.util.StructureUtil; import org.openepics.names.util.ValidateUtil; +import org.openepics.names.util.notification.NotificationUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; @@ -70,16 +71,16 @@ public class DeviceTypeService { } @Transactional(propagation = Propagation.MANDATORY) - public StructureElement deleteStructure(StructureElementCommand structureElement, - Date requested, String requestedBy) { + public StructureElementNotification deleteStructure(StructureElementCommand structureElement, + Date requested, String requestedBy, + HolderSystemDeviceStructure holder) { // validation outside method // transaction // do // find // create structure to pending, not latest, deleted, with data - // possibly validate that deleted // return - // structure element for deleted structure + // structure element for deleted structure + notification String uuid = structureElement.getUuid().toString(); @@ -97,11 +98,13 @@ public class DeviceTypeService { requested, requestedBy, structureElement.getComment()); deviceTypeRepository.createDeviceType(deviceType); - return StructureElementUtil.getStructureElementRequested(deviceType); + return new StructureElementNotification( + StructureElementUtil.getStructureElementRequested(deviceType), + NotificationUtil.prepareNotification(Type.DEVICETYPE, StructureCommand.DELETE, null, null, deviceType, holder)); } @Transactional(propagation = Propagation.MANDATORY) - public StructureElement approveStructure(StructureElementCommand structureElement, + public StructureElementNotification approveStructure(StructureElementCommand structureElement, Date processed, String processedBy, HolderSystemDeviceStructure holder) { // validation outside method @@ -111,7 +114,6 @@ public class DeviceTypeService { // update not latest // find // approve - update structure to status APPROVED, latest to true - // possibly validate that approved // additional // find out previous to find out what is approved - create update delete // approve create @@ -128,7 +130,7 @@ public class DeviceTypeService { // keep related names - legacy names // delete sub structures // return - // structure element for approved structure + // structure element for approved structure + notification String uuid = structureElement.getUuid().toString(); String processedComment = structureElement.getComment(); @@ -156,15 +158,13 @@ public class DeviceTypeService { deviceType.setLatest(Boolean.TRUE); deviceTypeRepository.updateDeviceType(deviceType); - // additional - // find out previous + // previous List<DeviceType> previouses = iDeviceTypeRepository.findPreviousByUuidAndId(uuid, deviceType.getId()); StructureCommand structureCommandCUD = StructureUtil.getStructureCommandCUD(previouses, deviceType); DeviceType previous = previouses != null && !previouses.isEmpty() ? previouses.get(0) : null; LOGGER.log(Level.FINE, "approveStructure, structureCommandCUD: {0}", structureCommandCUD); - // approve update - // approve delete + // additional if (StructureCommand.UPDATE.equals(structureCommandCUD)) { namesService.updateNames(previous, deviceType); } else if (StructureCommand.DELETE.equals(structureCommandCUD)) { @@ -172,7 +172,9 @@ public class DeviceTypeService { } LOGGER.log(Level.FINE, "approveStructure, structureElement: {0}", structureElement); - return StructureElementUtil.getStructureElementProcessed(deviceType, holder, StructureChoice.STRUCTURE); + return new StructureElementNotification( + StructureElementUtil.getStructureElementProcessed(deviceType, holder, StructureChoice.STRUCTURE), + NotificationUtil.prepareNotification(Type.DEVICETYPE, structureCommandCUD, StructureCommand.APPROVE, previous, deviceType, holder)); } } diff --git a/src/main/java/org/openepics/names/service/DisciplineService.java b/src/main/java/org/openepics/names/service/DisciplineService.java index cccf5e8880a9e1326ae2ab8c4e21ca4c1783436c..a1e4762a5ef24ea4691c83c7e1bbd14187e9713b 100644 --- a/src/main/java/org/openepics/names/service/DisciplineService.java +++ b/src/main/java/org/openepics/names/service/DisciplineService.java @@ -30,7 +30,6 @@ import org.openepics.names.repository.model.DeviceGroup; import org.openepics.names.repository.model.Discipline; import org.openepics.names.rest.beans.Status; import org.openepics.names.rest.beans.Type; -import org.openepics.names.rest.beans.element.StructureElement; import org.openepics.names.rest.beans.element.StructureElementCommand; import org.openepics.names.util.HolderSystemDeviceStructure; import org.openepics.names.util.StructureChoice; @@ -38,6 +37,7 @@ import org.openepics.names.util.StructureCommand; import org.openepics.names.util.StructureElementUtil; import org.openepics.names.util.StructureUtil; import org.openepics.names.util.ValidateUtil; +import org.openepics.names.util.notification.NotificationUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; @@ -81,16 +81,16 @@ public class DisciplineService { } @Transactional(propagation = Propagation.MANDATORY) - public StructureElement deleteStructure(StructureElementCommand structureElement, - Date requested, String requestedBy) { + public StructureElementNotification deleteStructure(StructureElementCommand structureElement, + Date requested, String requestedBy, + HolderSystemDeviceStructure holder) { // validation outside method // transaction // do // find // create structure to pending, not latest, deleted, with data - // possibly validate that deleted // return - // structure element for deleted structure + // structure element for deleted structure + notification String uuid = structureElement.getUuid().toString(); @@ -108,11 +108,13 @@ public class DisciplineService { requested, requestedBy, structureElement.getComment()); disciplineRepository.createDiscipline(discipline); - return StructureElementUtil.getStructureElementRequested(discipline); + return new StructureElementNotification( + StructureElementUtil.getStructureElementRequested(discipline), + NotificationUtil.prepareNotification(Type.DISCIPLINE, StructureCommand.DELETE, null, null, discipline, holder)); } @Transactional(propagation = Propagation.MANDATORY) - public StructureElement approveStructure(StructureElementCommand structureElement, + public StructureElementNotification approveStructure(StructureElementCommand structureElement, Date processed, String processedBy, HolderSystemDeviceStructure holder) { // validation outside method @@ -122,7 +124,6 @@ public class DisciplineService { // update not latest // find // approve - update structure to status APPROVED, latest to true - // possibly validate that approved // additional // find out previous to find out what is approved - create update delete // approve create @@ -139,7 +140,7 @@ public class DisciplineService { // keep related names - legacy names // delete sub structures // return - // structure element for approved structure + // structure element for approved structure + notification String uuid = structureElement.getUuid().toString(); String processedComment = structureElement.getComment(); @@ -166,15 +167,13 @@ public class DisciplineService { discipline.setLatest(Boolean.TRUE); disciplineRepository.updateDiscipline(discipline); - // additional - // find out previous + // previous List<Discipline> previouses = iDisciplineRepository.findPreviousByUuidAndId(uuid, discipline.getId()); StructureCommand structureCommandCUD = StructureUtil.getStructureCommandCUD(previouses, discipline); Discipline previous = previouses != null && !previouses.isEmpty() ? previouses.get(0) : null; LOGGER.log(Level.FINE, "approveStructure, structureCommandCUD: {0}", structureCommandCUD); - // approve update - // approve delete + // additional if (StructureCommand.UPDATE.equals(structureCommandCUD)) { namesService.updateNames(previous, discipline); } else if (StructureCommand.DELETE.equals(structureCommandCUD)) { @@ -186,7 +185,7 @@ public class DisciplineService { structureElementCommands.add(new StructureElementCommand(deviceGroup.getUuid(), Type.DEVICEGROUP, null, null, null, null, StructuresService.DELETE_AFTER_APPROVE_STRUCTURE_CHANGE)); } for (StructureElementCommand structureElementCommand : structureElementCommands) { - deviceGroupService.deleteStructure(structureElementCommand, processed, processedBy); + deviceGroupService.deleteStructure(structureElementCommand, processed, processedBy, holder); } for (StructureElementCommand structureElementCommand : structureElementCommands) { deviceGroupService.approveStructure(structureElementCommand, processed, processedBy, holder); @@ -194,7 +193,9 @@ public class DisciplineService { } LOGGER.log(Level.FINE, "approveStructure, structureElement: {0}", structureElement); - return StructureElementUtil.getStructureElementProcessed(discipline, holder, StructureChoice.STRUCTURE); + return new StructureElementNotification( + StructureElementUtil.getStructureElementProcessed(discipline, holder, StructureChoice.STRUCTURE), + NotificationUtil.prepareNotification(Type.DISCIPLINE, structureCommandCUD, StructureCommand.APPROVE, previous, discipline, holder)); } } diff --git a/src/main/java/org/openepics/names/service/NamesService.java b/src/main/java/org/openepics/names/service/NamesService.java index 0739f4d0adde20089ce189706154ce096e212561..2bfee4fe123e3f00310c32a976e90c46b20dd2c0 100644 --- a/src/main/java/org/openepics/names/service/NamesService.java +++ b/src/main/java/org/openepics/names/service/NamesService.java @@ -147,6 +147,7 @@ public class NamesService { // for each name element // create name, latest, with data // add + // no notify // return // name elements for created names @@ -176,6 +177,7 @@ public class NamesService { // find // prepare // create + // approved, latest, not deleted, uuid // return // name element for name @@ -210,9 +212,6 @@ public class NamesService { requested, requestedBy, nameElement.getComment()); nameRepository.createName(name); - // possibly validate that created - // approved, latest, not deleted, uuid - // add return NameElementUtil.getNameElement(name); } @@ -558,8 +557,8 @@ public class NamesService { // update name to not latest // find // insert name to latest, not deleted, with data - // possibly validate that updated // add + // no notify // return // name elements for updated names @@ -746,8 +745,8 @@ public class NamesService { // update name to not latest // find // insert name to latest, deleted - // possibly validate that deleted // add + // no notify // return // name element for deleted names diff --git a/src/main/java/org/openepics/names/service/NotificationService.java b/src/main/java/org/openepics/names/service/NotificationService.java new file mode 100644 index 0000000000000000000000000000000000000000..75dcadee9ed08ca221e32d10b26db366720df9e9 --- /dev/null +++ b/src/main/java/org/openepics/names/service/NotificationService.java @@ -0,0 +1,261 @@ +/* + * 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.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang3.StringUtils; +import org.openepics.names.util.NameCommand; +import org.openepics.names.util.StructureCommand; +import org.openepics.names.util.notification.NotificationName; +import org.openepics.names.util.notification.NotificationStructure; +import org.openepics.names.util.notification.NotificationUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring5.SpringTemplateEngine; +import org.thymeleaf.templatemode.TemplateMode; +import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; + +/** + * This class provides notification services. + * + * @author Lars Johansson + */ +@Service +public class NotificationService { + + private static final Logger LOGGER = Logger.getLogger(NotificationService.class.getName()); + + private static final String NOTIFY_NAMES_CREATED_UPDATED_DELETED = + "Notify names, # created: {0}, # updated: {1}, # deleted: {2}"; + private static final String NOTIFY_STRUCTURES_CREATED_UPDATED_DELETED_APPROVED_CANCELLED_REJECTED = + "Notify structures, # created: {0}, # updated: {1}, # deleted: {2}" + + ", # approved: {3}, # cancelled: {4}, # rejected: {5}"; + + private static final String CHANGES_NOTIFICATION_NAMES = "[Changes for names]"; + private static final String CHANGES_NOTIFICATION_STRUCTURES = "[Changes for structures]"; + private static final String CHANGES_NOTIFICATION_STRUCTURES_ACTION = "[Changes for structures - need action]"; + + private static final String ADD_FOOTER = "addfooter"; + private static final String BACKEND_URL = "backendurl"; + private static final String COMMA = ","; + + @Value("${naming.backend.swagger.url}") + String namingBackendSwaggerUrl; + @Value("${naming.mail.administrator}") + String namingMailAdministrator; + + @Autowired + private MailService mailService; + + /** + * Send notifications for names to affected users and administrators by email. + * + * @param notifications list of name notifications + * @param nameCommand name command + */ + public void notifyNames(List<NotificationName> notifications, NameCommand nameCommand) { + if (NameCommand.CREATE.equals(nameCommand)) { + notifyNames(notifications, null, null); + } else if (NameCommand.UPDATE.equals(nameCommand)) { + notifyNames(null, notifications, null); + } else if (NameCommand.DELETE.equals(nameCommand)) { + notifyNames(null, null, notifications); + } + } + + /** + * Send notifications for names to affected users and administrators by email. + * + * @param created list of notifications for created names + * @param updated list of notifications for updated names + * @param deleted list of notifications for deleted names + */ + public void notifyNames( + List<NotificationName> created, + List<NotificationName> updated, + List<NotificationName> deleted) { + + NotificationUtil.sortByNewName(created); + NotificationUtil.sortByNewName(updated); + NotificationUtil.sortByNewName(deleted); + + int numberCreated = getListSize(created); + int numberUpdated = getListSize(updated); + int numberDeleted = getListSize(deleted); + + LOGGER.log(Level.INFO, + () -> MessageFormat.format( + NOTIFY_NAMES_CREATED_UPDATED_DELETED, + numberCreated, + numberUpdated, + numberDeleted)); + + // email content + final Context ctx = new Context(); + ctx.setVariable("numberCreated", numberCreated); + ctx.setVariable("numberUpdated", numberUpdated); + ctx.setVariable("numberDeleted", numberDeleted); + ctx.setVariable("created", created); + ctx.setVariable("updated", updated); + ctx.setVariable("deleted", deleted); + ctx.setVariable(ADD_FOOTER, true); + ctx.setVariable(BACKEND_URL, namingBackendSwaggerUrl); + TemplateEngine engine = generateTemplateEngine(); + + // send notification + String[] toEmailAddresses = !StringUtils.isEmpty(namingMailAdministrator) + ? namingMailAdministrator.split(COMMA) + : null; + String[] ccEmailAddresses = null; + String[] replyToEmailAddresses = null; + String subject = CHANGES_NOTIFICATION_NAMES + " - " + namingBackendSwaggerUrl; + mailService.sendEmail(toEmailAddresses, ccEmailAddresses, replyToEmailAddresses, + subject, engine.process("templates/notification_names.html", ctx), false, null, null); + } + + /** + * Send notifications for structures to affected users and administrators by email. + * + * @param notifications list of structure notifications + * @param structureCommand structure command + */ + public void notifyStructures(List<NotificationStructure> notifications, StructureCommand structureCommand) { + if (StructureCommand.CREATE.equals(structureCommand)) { + notifyStructures(notifications, null, null, null, null, null); + } else if (StructureCommand.UPDATE.equals(structureCommand)) { + notifyStructures(null, notifications, null, null, null, null); + } else if (StructureCommand.DELETE.equals(structureCommand)) { + notifyStructures(null, null, notifications, null, null, null); + } else if (StructureCommand.APPROVE.equals(structureCommand)) { + notifyStructures(null, null, null, notifications, null, null); + } else if (StructureCommand.CANCEL.equals(structureCommand)) { + notifyStructures(null, null, null, null, notifications, null); + } else if (StructureCommand.REJECT.equals(structureCommand)) { + notifyStructures(null, null, null, null, null, notifications); + } + } + + /** + * Send notifications for structures to affected users and administrators by email. + * + * @param created list of notifications for created structures + * @param updated list of notifications for updated structures + * @param deleted list of notifications for deleted structures + * @param approved list of notifications for approved structures + * @param cancelled list of notifications for cancelled structures + * @param rejected list of notifications for rejected structures + */ + public void notifyStructures( + List<NotificationStructure> created, + List<NotificationStructure> updated, + List<NotificationStructure> deleted, + List<NotificationStructure> approved, + List<NotificationStructure> cancelled, + List<NotificationStructure> rejected) { + + NotificationUtil.sortByNewMnemonicpath(created); + NotificationUtil.sortByNewMnemonicpath(updated); + NotificationUtil.sortByNewMnemonicpath(deleted); + NotificationUtil.sortByNewMnemonicpath(approved); + NotificationUtil.sortByNewMnemonicpath(cancelled); + NotificationUtil.sortByNewMnemonicpath(rejected); + + int numberCreated = getListSize(created); + int numberUpdated = getListSize(updated); + int numberDeleted = getListSize(deleted); + int numberApproved = getListSize(approved); + int numberCancelled = getListSize(cancelled); + int numberRejected = getListSize(rejected); + + LOGGER.log(Level.INFO, + () -> MessageFormat.format( + NOTIFY_STRUCTURES_CREATED_UPDATED_DELETED_APPROVED_CANCELLED_REJECTED, + numberCreated, + numberUpdated, + numberDeleted, + numberApproved, + numberCancelled, + numberRejected)); + + // email content + final Context ctx = new Context(); + ctx.setVariable("numberCreated", numberCreated); + ctx.setVariable("numberUpdated", numberUpdated); + ctx.setVariable("numberDeleted", numberDeleted); + ctx.setVariable("numberApproved", numberApproved); + ctx.setVariable("numberCancelled", numberCancelled); + ctx.setVariable("numberRejected", numberRejected); + ctx.setVariable("created", created); + ctx.setVariable("updated", updated); + ctx.setVariable("deleted", deleted); + ctx.setVariable("approved", approved); + ctx.setVariable("cancelled", cancelled); + ctx.setVariable("rejected", rejected); + ctx.setVariable(ADD_FOOTER, true); + ctx.setVariable(BACKEND_URL, namingBackendSwaggerUrl); + TemplateEngine engine = generateTemplateEngine(); + + // send notification + String[] toEmailAddresses = !StringUtils.isEmpty(namingMailAdministrator) + ? namingMailAdministrator.split(COMMA) + : null; + String[] ccEmailAddresses = null; + String[] replyToEmailAddresses = null; + String subject = numberCreated > 0 || numberUpdated > 0 || numberDeleted > 0 + ? CHANGES_NOTIFICATION_STRUCTURES_ACTION + " - " + namingBackendSwaggerUrl + : CHANGES_NOTIFICATION_STRUCTURES + " - " + namingBackendSwaggerUrl; + mailService.sendEmail(toEmailAddresses, ccEmailAddresses, replyToEmailAddresses, + subject, engine.process("templates/notification_structures.html", ctx), false, null, null); + } + + /** + * Return list size, zero if empty list. + * + * @param list list + * @return list size + */ + private int getListSize(List<?> list) { + return list != null ? list.size() : 0; + } + + /** + * Generates the Thymeleaf template engine + * + * @return generate template engine + */ + private TemplateEngine generateTemplateEngine() { + ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); + templateResolver.setTemplateMode(TemplateMode.HTML); + templateResolver.setCacheable(false); + templateResolver.setCharacterEncoding(StandardCharsets.UTF_8.name()); + + TemplateEngine engine = new SpringTemplateEngine(); + engine.setTemplateResolver(templateResolver); + return engine; + } + +} diff --git a/src/main/java/org/openepics/names/service/StructureElementNotification.java b/src/main/java/org/openepics/names/service/StructureElementNotification.java new file mode 100644 index 0000000000000000000000000000000000000000..7ce9a9a7dbe935e3303a279db3449cecf1a1f7d6 --- /dev/null +++ b/src/main/java/org/openepics/names/service/StructureElementNotification.java @@ -0,0 +1,30 @@ +/* + * 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 org.openepics.names.rest.beans.element.StructureElement; +import org.openepics.names.util.notification.NotificationStructure; + +/** + * Utility record to collect structure element and notification. + * Intended for use in service package classes only. + * + * @author Lars Johansson + */ +record StructureElementNotification (StructureElement structureElement, NotificationStructure notificationStructure) {} diff --git a/src/main/java/org/openepics/names/service/StructuresService.java b/src/main/java/org/openepics/names/service/StructuresService.java index 6b070de38e44a8970cd1b3a7c1b44a4465c2f83c..1e5a4349cbdd783bfc6745404f0d10724671f9de 100644 --- a/src/main/java/org/openepics/names/service/StructuresService.java +++ b/src/main/java/org/openepics/names/service/StructuresService.java @@ -56,11 +56,14 @@ import org.openepics.names.util.HolderIRepositories; import org.openepics.names.util.HolderRepositories; import org.openepics.names.util.HolderSystemDeviceStructure; import org.openepics.names.util.StructureChoice; +import org.openepics.names.util.StructureCommand; import org.openepics.names.util.StructureElementUtil; import org.openepics.names.util.TextUtil; import org.openepics.names.util.Utilities; import org.openepics.names.util.ValidateStructureElementUtil; import org.openepics.names.util.ValidateUtil; +import org.openepics.names.util.notification.NotificationStructure; +import org.openepics.names.util.notification.NotificationUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -101,6 +104,7 @@ public class StructuresService { private DisciplineService disciplineService; private DeviceGroupService deviceGroupService; private DeviceTypeService deviceTypeService; + private NotificationService notificationService; @Autowired public StructuresService( @@ -123,7 +127,8 @@ public class StructuresService { SubsystemService subsystemService, DisciplineService disciplineService, DeviceGroupService deviceGroupService, - DeviceTypeService deviceTypeService) { + DeviceTypeService deviceTypeService, + NotificationService notificationService) { this.namingConvention = new EssNamingConvention(); this.holderIRepositories = new HolderIRepositories( @@ -148,6 +153,7 @@ public class StructuresService { this.disciplineService = disciplineService; this.deviceGroupService = deviceGroupService; this.deviceTypeService = deviceTypeService; + this.notificationService = notificationService; } @Transactional @@ -155,22 +161,28 @@ public class StructuresService { // validation outside method // transaction // do - // for each structure element + // for each structure element command // create structure to pending, not latest, not deleted, with data - // possibly validate that created - // add structure element for created + // handle notification + // handle structure element for created structure + // notify // return // structure elements for created structures LOGGER.log(Level.FINE, "createStructures, structureElements.size: {0}", Utilities.getSize(structureElements)); + // initiate holder of containers for system and device structure content, for performance reasons + HolderSystemDeviceStructure holder = new HolderSystemDeviceStructure(holderIRepositories); + // do Date requested = new Date(); String requestedBy = TextUtil.TEST_WHO; + List<NotificationStructure> notifications = Lists.newArrayList(); final List<StructureElement> createdStructureElements = Lists.newArrayList(); for (StructureElementCommand structureElement : structureElements) { // note // namingConvention.equivalenceClassRepresentative return null for null input + // rules for mnemonic for system group, device group UUID uuid = UUID.randomUUID(); Type type = structureElement.getType(); @@ -182,8 +194,6 @@ public class StructuresService { String requestedComment = structureElement.getComment(); if (Type.SYSTEMGROUP.equals(type)) { - // note rules for mnemonic for system group - // create SystemGroup systemGroup = new SystemGroup(uuid, name, mnemonic, equivalenceClassRepresentative, @@ -191,7 +201,8 @@ public class StructuresService { requested, requestedBy, requestedComment); holderRepositories.getSystemGroupRepository().createSystemGroup(systemGroup); - // add + // handle + NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.CREATE, null, null, systemGroup, holder); createdStructureElements.add(StructureElementUtil.getStructureElementRequested(systemGroup)); } else if (Type.SYSTEM.equals(type)) { // create @@ -201,7 +212,8 @@ public class StructuresService { requested, requestedBy, requestedComment); holderRepositories.getSystemRepository().createSystem(system); - // add + // handle + NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.CREATE, null, null, system, holder); createdStructureElements.add(StructureElementUtil.getStructureElementRequested(system)); } else if (Type.SUBSYSTEM.equals(type)) { // create @@ -211,7 +223,8 @@ public class StructuresService { requested, requestedBy, requestedComment); holderRepositories.getSubsystemRepository().createSubsystem(subsystem); - // add + // handle + NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.CREATE, null, null, subsystem, holder); createdStructureElements.add(StructureElementUtil.getStructureElementRequested(subsystem)); } else if (Type.DISCIPLINE.equals(type)) { // create @@ -221,11 +234,10 @@ public class StructuresService { requested, requestedBy, requestedComment); holderRepositories.getDisciplineRepository().createDiscipline(discipline); - // add + // handle + NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.CREATE, null, null, discipline, holder); createdStructureElements.add(StructureElementUtil.getStructureElementRequested(discipline)); } else if (Type.DEVICEGROUP.equals(type)) { - // note rules for mnemonic for device group - // create DeviceGroup deviceGroup = new DeviceGroup(uuid, structureElement.getParent(), name, mnemonic, equivalenceClassRepresentative, @@ -233,7 +245,8 @@ public class StructuresService { requested, requestedBy, requestedComment); holderRepositories.getDeviceGroupRepository().createDeviceGroup(deviceGroup); - // add + // handle + NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.CREATE, null, null, deviceGroup, holder); createdStructureElements.add(StructureElementUtil.getStructureElementRequested(deviceGroup)); } else if (Type.DEVICETYPE.equals(type)) { // create @@ -243,11 +256,15 @@ public class StructuresService { requested, requestedBy, requestedComment); holderRepositories.getDeviceTypeRepository().createDeviceType(deviceType); - // add + // handle + NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.CREATE, null, null, deviceType, holder); createdStructureElements.add(StructureElementUtil.getStructureElementRequested(deviceType)); } } + // notify + notificationService.notifyStructures(notifications, StructureCommand.CREATE); + LOGGER.log(Level.FINE, "createStructures, createdStructureElements.size: {0}", createdStructureElements.size()); return createdStructureElements; } @@ -813,21 +830,29 @@ public class StructuresService { // validation outside method // transaction // do - // for each structure element + // for each structure element command // create structure to pending, not latest, not deleted, with data - // possibly validate that updated - // add structure element for updated + // find out previous + // handle notification + // handle structure element for updated structure + // notify // return // structure elements for updated structures LOGGER.log(Level.FINE, "updateStructures, structureElements.size: {0}", Utilities.getSize(structureElements)); + // initiate holder of containers for system and device structure content, for performance reasons + HolderSystemDeviceStructure holder = new HolderSystemDeviceStructure(holderIRepositories); + // do Date requested = new Date(); String requestedBy = TextUtil.TEST_WHO; + List<NotificationStructure> notifications = Lists.newArrayList(); final List<StructureElement> updatedStructureElements = Lists.newArrayList(); for (StructureElementCommand structureElement : structureElements) { - // namingConvention.equivalenceClassRepresentative return null for null input + // note + // namingConvention.equivalenceClassRepresentative return null for null input + // rules for mnemonic for system group, device group UUID uuid = structureElement.getUuid(); Type type = structureElement.getType(); @@ -839,8 +864,6 @@ public class StructuresService { String requestedComment = structureElement.getComment(); if (Type.SYSTEMGROUP.equals(type)) { - // note rules for mnemonic for system group - // create SystemGroup systemGroup = new SystemGroup(uuid, name, mnemonic, equivalenceClassRepresentative, @@ -848,7 +871,12 @@ public class StructuresService { requested, requestedBy, requestedComment); holderRepositories.getSystemGroupRepository().createSystemGroup(systemGroup); - // add + // previous + List<SystemGroup> previouses = holderIRepositories.getSystemGroupRepository().findPreviousByUuidAndId(uuid.toString(), systemGroup.getId()); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.UPDATE, null, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, systemGroup, holder); updatedStructureElements.add(StructureElementUtil.getStructureElementRequested(systemGroup)); } else if (Type.SYSTEM.equals(type)) { // create @@ -858,7 +886,12 @@ public class StructuresService { requested, requestedBy, requestedComment); holderRepositories.getSystemRepository().createSystem(system); - // add + // previous + List<System> previouses = holderIRepositories.getSystemRepository().findPreviousByUuidAndId(uuid.toString(), system.getId()); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.UPDATE, null, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, system, holder); updatedStructureElements.add(StructureElementUtil.getStructureElementRequested(system)); } else if (Type.SUBSYSTEM.equals(type)) { // create @@ -868,7 +901,12 @@ public class StructuresService { requested, requestedBy, requestedComment); holderRepositories.getSubsystemRepository().createSubsystem(subsystem); - // add + // previous + List<Subsystem> previouses = holderIRepositories.getSubsystemRepository().findPreviousByUuidAndId(uuid.toString(), subsystem.getId()); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.UPDATE, null, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, subsystem, holder); updatedStructureElements.add(StructureElementUtil.getStructureElementRequested(subsystem)); } else if (Type.DISCIPLINE.equals(type)) { // create @@ -878,11 +916,14 @@ public class StructuresService { requested, requestedBy, requestedComment); holderRepositories.getDisciplineRepository().createDiscipline(discipline); - // add + // previous + List<Discipline> previouses = holderIRepositories.getDisciplineRepository().findPreviousByUuidAndId(uuid.toString(), discipline.getId()); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.UPDATE, null, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, discipline, holder); updatedStructureElements.add(StructureElementUtil.getStructureElementRequested(discipline)); } else if (Type.DEVICEGROUP.equals(type)) { - // note rules for mnemonic for device group - // create DeviceGroup deviceGroup = new DeviceGroup(uuid, structureElement.getParent(), name, mnemonic, equivalenceClassRepresentative, @@ -890,7 +931,12 @@ public class StructuresService { requested, requestedBy, requestedComment); holderRepositories.getDeviceGroupRepository().createDeviceGroup(deviceGroup); - // add + // previous + List<DeviceGroup> previouses = holderIRepositories.getDeviceGroupRepository().findPreviousByUuidAndId(uuid.toString(), deviceGroup.getId()); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.UPDATE, null, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, deviceGroup, holder); updatedStructureElements.add(StructureElementUtil.getStructureElementRequested(deviceGroup)); } else if (Type.DEVICETYPE.equals(type)) { // create @@ -900,11 +946,19 @@ public class StructuresService { requested, requestedBy, requestedComment); holderRepositories.getDeviceTypeRepository().createDeviceType(deviceType); - // add + // previous + List<DeviceType> previouses = holderIRepositories.getDeviceTypeRepository().findPreviousByUuidAndId(uuid.toString(), deviceType.getId()); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, StructureCommand.UPDATE, null, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, deviceType, holder); updatedStructureElements.add(StructureElementUtil.getStructureElementRequested(deviceType)); } } + // notify + notificationService.notifyStructures(notifications, StructureCommand.UPDATE); + LOGGER.log(Level.FINE, "updateStructures, updatedStructureElements.size: {0}", updatedStructureElements.size()); return updatedStructureElements; } @@ -916,44 +970,51 @@ public class StructuresService { // validation outside method // transaction // do - // for each structure element - // find - // create structure to pending, not latest, deleted, with data - // possibly validate that deleted - // add structure element for deleted + // for each structure element command + // delete structure + // handle notification + // handle structure element for deleted structure + // notify // return // structure elements for deleted structures LOGGER.log(Level.FINE, "deleteStructures, structureElements.size: {0}", Utilities.getSize(structureElements)); + // initiate holder of containers for system and device structure content, for performance reasons + HolderSystemDeviceStructure holder = new HolderSystemDeviceStructure(holderIRepositories); + // do Date requested = new Date(); String requestedBy = TextUtil.TEST_WHO; + List<NotificationStructure> notifications = Lists.newArrayList(); final List<StructureElement> deletedStructureElements = Lists.newArrayList(); for (StructureElementCommand structureElement : structureElements) { Type type = structureElement.getType(); + StructureElementNotification structureElementNotification = null; if (Type.SYSTEMGROUP.equals(type)) { - Utilities.addToCollection(deletedStructureElements, - systemGroupService.deleteStructure(structureElement, requested, requestedBy)); + structureElementNotification = systemGroupService.deleteStructure(structureElement, requested, requestedBy, holder); } else if (Type.SYSTEM.equals(type)) { - Utilities.addToCollection(deletedStructureElements, - systemService.deleteStructure(structureElement, requested, requestedBy)); + structureElementNotification = systemService.deleteStructure(structureElement, requested, requestedBy, holder); } else if (Type.SUBSYSTEM.equals(type)) { - Utilities.addToCollection(deletedStructureElements, - subsystemService.deleteStructure(structureElement, requested, requestedBy)); + structureElementNotification = subsystemService.deleteStructure(structureElement, requested, requestedBy, holder); } else if (Type.DISCIPLINE.equals(type)) { - Utilities.addToCollection(deletedStructureElements, - disciplineService.deleteStructure(structureElement, requested, requestedBy)); + structureElementNotification = disciplineService.deleteStructure(structureElement, requested, requestedBy, holder); } else if (Type.DEVICEGROUP.equals(type)) { - Utilities.addToCollection(deletedStructureElements, - deviceGroupService.deleteStructure(structureElement, requested, requestedBy)); + structureElementNotification = deviceGroupService.deleteStructure(structureElement, requested, requestedBy, holder); } else if (Type.DEVICETYPE.equals(type)) { - Utilities.addToCollection(deletedStructureElements, - deviceTypeService.deleteStructure(structureElement, requested, requestedBy)); + structureElementNotification = deviceTypeService.deleteStructure(structureElement, requested, requestedBy, holder); + } else { + continue; } + + Utilities.addToCollection(notifications, structureElementNotification.notificationStructure()); + Utilities.addToCollection(deletedStructureElements, structureElementNotification.structureElement()); } + // notify + notificationService.notifyStructures(notifications, StructureCommand.DELETE); + LOGGER.log(Level.FINE, "deleteStructures, deletedStructureElements.size: {0}", deletedStructureElements.size()); return deletedStructureElements; } @@ -965,28 +1026,11 @@ public class StructuresService { // validation outside method // transaction // do - // for each structure element + // for each structure element command // approve structure - // find - // update not latest - // find - // approve - update structure to status APPROVED, latest to true - // possibly validate that approved - // additional - // find out previous to find out what is approved - create update delete - // approve create - // automatically create name when system structure is approved - // condition on name and structure entry - // system structure should exist (uuid), one entry that is not deleted - // name should not exist (system structure mnemonic) - // within current transaction - // approve update - // update related names - // for different mnemonic - // not for device group - // approve delete - // keep related names - legacy names - // delete sub structures + // handle notification + // handle structure element for approved structure + // notify // return // structure elements for approved structures @@ -1003,36 +1047,37 @@ public class StructuresService { // delete sub structures // do - // update - // set not latest for current latest - // set approved, latest for pending Date processed = new Date(); String processedBy = TextUtil.TEST_WHO; + List<NotificationStructure> notifications = Lists.newArrayList(); final List<StructureElement> approvedStructureElements = Lists.newArrayList(); for (StructureElementCommand structureElement : structureElements) { Type type = structureElement.getType(); + StructureElementNotification structureElementNotification = null; if (Type.SYSTEMGROUP.equals(type)) { - Utilities.addToCollection(approvedStructureElements, - systemGroupService.approveStructure(structureElement, processed, processedBy, holder)); + structureElementNotification = systemGroupService.approveStructure(structureElement, processed, processedBy, holder); } else if (Type.SYSTEM.equals(type)) { - Utilities.addToCollection(approvedStructureElements, - systemService.approveStructure(structureElement, processed, processedBy, holder)); + structureElementNotification = systemService.approveStructure(structureElement, processed, processedBy, holder); } else if (Type.SUBSYSTEM.equals(type)) { - Utilities.addToCollection(approvedStructureElements, - subsystemService.approveStructure(structureElement, processed, processedBy, holder)); + structureElementNotification = subsystemService.approveStructure(structureElement, processed, processedBy, holder); } else if (Type.DISCIPLINE.equals(type)) { - Utilities.addToCollection(approvedStructureElements, - disciplineService.approveStructure(structureElement, processed, processedBy, holder)); + structureElementNotification = disciplineService.approveStructure(structureElement, processed, processedBy, holder); } else if (Type.DEVICEGROUP.equals(type)) { - Utilities.addToCollection(approvedStructureElements, - deviceGroupService.approveStructure(structureElement, processed, processedBy, holder)); + structureElementNotification = deviceGroupService.approveStructure(structureElement, processed, processedBy, holder); } else if (Type.DEVICETYPE.equals(structureElement.getType())) { - Utilities.addToCollection(approvedStructureElements, - deviceTypeService.approveStructure(structureElement, processed, processedBy, holder)); + structureElementNotification = deviceTypeService.approveStructure(structureElement, processed, processedBy, holder); + } else { + continue; } + + Utilities.addToCollection(notifications, structureElementNotification.notificationStructure()); + Utilities.addToCollection(approvedStructureElements, structureElementNotification.structureElement()); } + // notify + notificationService.notifyStructures(notifications, StructureCommand.APPROVE); + LOGGER.log(Level.FINE, "approveStructures, approvedStructureElements.size: {0}", approvedStructureElements.size()); return approvedStructureElements; } @@ -1045,8 +1090,10 @@ public class StructuresService { // for each structure element // find // cancel - update structure to status CANCELLED - // possibly validate that cancelled - // add + // find out previous + // handle notification + // handle structure element for cancelled structure + // notify // return // structure elements for cancelled structures @@ -1058,6 +1105,7 @@ public class StructuresService { // do Date processed = new Date(); String processedBy = TextUtil.TEST_WHO; + List<NotificationStructure> notifications = Lists.newArrayList(); final List<StructureElement> cancelledStructureElements = Lists.newArrayList(); for (StructureElementCommand structureElement : structureElements) { String uuid = structureElement.getUuid().toString(); @@ -1076,7 +1124,13 @@ public class StructuresService { systemGroup.setAttributesStatusProcessed(Status.CANCELLED, processed, processedBy, processedComment); holderRepositories.getSystemGroupRepository().updateSystemGroup(systemGroup); - // add + // previous + List<SystemGroup> previouses = holderIRepositories.getSystemGroupRepository().findPreviousByUuidAndId(uuid, systemGroup.getId()); + StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, systemGroup); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.CANCEL, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, systemGroup, holder); cancelledStructureElements.add(StructureElementUtil.getStructureElementProcessed(systemGroup, holder, StructureChoice.STRUCTURE)); } else if (Type.SYSTEM.equals(type)) { // find @@ -1090,7 +1144,13 @@ public class StructuresService { system.setAttributesStatusProcessed(Status.CANCELLED, processed, processedBy, processedComment); holderRepositories.getSystemRepository().updateSystem(system); - // add + // previous + List<System> previouses = holderIRepositories.getSystemRepository().findPreviousByUuidAndId(uuid, system.getId()); + StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, system); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.CANCEL, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, system, holder); cancelledStructureElements.add(StructureElementUtil.getStructureElementProcessed(system, holder, StructureChoice.STRUCTURE)); } else if (Type.SUBSYSTEM.equals(type)) { // find @@ -1104,7 +1164,13 @@ public class StructuresService { subsystem.setAttributesStatusProcessed(Status.CANCELLED, processed, processedBy, processedComment); holderRepositories.getSubsystemRepository().updateSubsystem(subsystem); - // add + // previous + List<Subsystem> previouses = holderIRepositories.getSubsystemRepository().findPreviousByUuidAndId(uuid, subsystem.getId()); + StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, subsystem); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.CANCEL, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, subsystem, holder); cancelledStructureElements.add(StructureElementUtil.getStructureElementProcessed(subsystem, holder, StructureChoice.STRUCTURE)); } else if (Type.DISCIPLINE.equals(type)) { // find @@ -1118,7 +1184,13 @@ public class StructuresService { discipline.setAttributesStatusProcessed(Status.CANCELLED, processed, processedBy, processedComment); holderRepositories.getDisciplineRepository().updateDiscipline(discipline); - // add + // previous + List<Discipline> previouses = holderIRepositories.getDisciplineRepository().findPreviousByUuidAndId(uuid, discipline.getId()); + StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, discipline); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.CANCEL, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, discipline, holder); cancelledStructureElements.add(StructureElementUtil.getStructureElementProcessed(discipline, holder, StructureChoice.STRUCTURE)); } else if (Type.DEVICEGROUP.equals(type)) { // find @@ -1132,7 +1204,13 @@ public class StructuresService { deviceGroup.setAttributesStatusProcessed(Status.CANCELLED, processed, processedBy, processedComment); holderRepositories.getDeviceGroupRepository().updateDeviceGroup(deviceGroup); - // add + // previous + List<DeviceGroup> previouses = holderIRepositories.getDeviceGroupRepository().findPreviousByUuidAndId(uuid, deviceGroup.getId()); + StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, deviceGroup); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.CANCEL, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, deviceGroup, holder); cancelledStructureElements.add(StructureElementUtil.getStructureElementProcessed(deviceGroup, holder, StructureChoice.STRUCTURE)); } else if (Type.DEVICETYPE.equals(type)) { List<DeviceType> deviceTypes = holderRepositories.getDeviceTypeRepository().readDeviceTypes(Status.PENDING, null, uuid, null, null, null, null, null, null); @@ -1145,11 +1223,20 @@ public class StructuresService { deviceType.setAttributesStatusProcessed(Status.CANCELLED, processed, processedBy, processedComment); holderRepositories.getDeviceTypeRepository().updateDeviceType(deviceType); - // add + // previous + List<DeviceType> previouses = holderIRepositories.getDeviceTypeRepository().findPreviousByUuidAndId(uuid, deviceType.getId()); + StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, deviceType); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.CANCEL, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, deviceType, holder); cancelledStructureElements.add(StructureElementUtil.getStructureElementProcessed(deviceType, holder, StructureChoice.STRUCTURE)); } } + // notify + notificationService.notifyStructures(notifications, StructureCommand.CANCEL); + LOGGER.log(Level.FINE, "cancelStructures, cancelledStructureElements.size: {0}", cancelledStructureElements.size()); return cancelledStructureElements; } @@ -1162,8 +1249,10 @@ public class StructuresService { // for each structure element // find // reject - update structure to status REJECTED - // possibly validate that rejected - // add + // find out previous + // handle notification + // handle structure element for rejected structure + // notify // return // structure elements for rejected structures @@ -1175,6 +1264,7 @@ public class StructuresService { // do Date processed = new Date(); String processedBy = TextUtil.TEST_WHO; + List<NotificationStructure> notifications = Lists.newArrayList(); final List<StructureElement> rejectedStructureElements = Lists.newArrayList(); for (StructureElementCommand structureElement : structureElements) { String uuid = structureElement.getUuid().toString(); @@ -1193,7 +1283,13 @@ public class StructuresService { systemGroup.setAttributesStatusProcessed(Status.REJECTED, processed, processedBy, processedComment); holderRepositories.getSystemGroupRepository().updateSystemGroup(systemGroup); - // add + // previous + List<SystemGroup> previouses = holderIRepositories.getSystemGroupRepository().findPreviousByUuidAndId(uuid, systemGroup.getId()); + StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, systemGroup); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.REJECT, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, systemGroup, holder); rejectedStructureElements.add(StructureElementUtil.getStructureElementProcessed(systemGroup, holder, StructureChoice.STRUCTURE)); } else if (Type.SYSTEM.equals(type)) { // find @@ -1207,7 +1303,13 @@ public class StructuresService { system.setAttributesStatusProcessed(Status.REJECTED, processed, processedBy, processedComment); holderRepositories.getSystemRepository().updateSystem(system); - // add + // previous + List<System> previouses = holderIRepositories.getSystemRepository().findPreviousByUuidAndId(uuid, system.getId()); + StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, system); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.REJECT, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, system, holder); rejectedStructureElements.add(StructureElementUtil.getStructureElementProcessed(system, holder, StructureChoice.STRUCTURE)); } else if (Type.SUBSYSTEM.equals(type)) { // find @@ -1221,7 +1323,13 @@ public class StructuresService { subsystem.setAttributesStatusProcessed(Status.REJECTED, processed, processedBy, processedComment); holderRepositories.getSubsystemRepository().updateSubsystem(subsystem); - // add + // previous + List<Subsystem> previouses = holderIRepositories.getSubsystemRepository().findPreviousByUuidAndId(uuid, subsystem.getId()); + StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, subsystem); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.REJECT, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, subsystem, holder); rejectedStructureElements.add(StructureElementUtil.getStructureElementProcessed(subsystem, holder, StructureChoice.STRUCTURE)); } else if (Type.DISCIPLINE.equals(type)) { // find @@ -1235,7 +1343,13 @@ public class StructuresService { discipline.setAttributesStatusProcessed(Status.REJECTED, processed, processedBy, processedComment); holderRepositories.getDisciplineRepository().updateDiscipline(discipline); - // add + // previous + List<Discipline> previouses = holderIRepositories.getDisciplineRepository().findPreviousByUuidAndId(uuid, discipline.getId()); + StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, discipline); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.REJECT, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, discipline, holder); rejectedStructureElements.add(StructureElementUtil.getStructureElementProcessed(discipline, holder, StructureChoice.STRUCTURE)); } else if (Type.DEVICEGROUP.equals(type)) { // find @@ -1249,7 +1363,13 @@ public class StructuresService { deviceGroup.setAttributesStatusProcessed(Status.REJECTED, processed, processedBy, processedComment); holderRepositories.getDeviceGroupRepository().updateDeviceGroup(deviceGroup); - // add + // previous + List<DeviceGroup> previouses = holderIRepositories.getDeviceGroupRepository().findPreviousByUuidAndId(uuid, deviceGroup.getId()); + StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, deviceGroup); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.REJECT, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, deviceGroup, holder); rejectedStructureElements.add(StructureElementUtil.getStructureElementProcessed(deviceGroup, holder, StructureChoice.STRUCTURE)); } else if (Type.DEVICETYPE.equals(type)) { // find @@ -1263,11 +1383,20 @@ public class StructuresService { deviceType.setAttributesStatusProcessed(Status.REJECTED, processed, processedBy, processedComment); holderRepositories.getDeviceTypeRepository().updateDeviceType(deviceType); - // add + // previous + List<DeviceType> previouses = holderIRepositories.getDeviceTypeRepository().findPreviousByUuidAndId(uuid, deviceType.getId()); + StructureCommand structureCommandCUD = NotificationUtil.getStructureCommandCUD(previouses, deviceType); + + // handle + NotificationUtil.prepareAddNotification(notifications, type, structureCommandCUD, StructureCommand.REJECT, + previouses != null && !previouses.isEmpty() ? previouses.get(0) : null, deviceType, holder); rejectedStructureElements.add(StructureElementUtil.getStructureElementProcessed(deviceType, holder, StructureChoice.STRUCTURE)); } } + // notify + notificationService.notifyStructures(notifications, StructureCommand.REJECT); + LOGGER.log(Level.FINE, "rejectStructures, rejectedStructureElements.size: {0}", rejectedStructureElements.size()); return rejectedStructureElements; } diff --git a/src/main/java/org/openepics/names/service/SubsystemService.java b/src/main/java/org/openepics/names/service/SubsystemService.java index 77424b9ba9fc30f4431a623f5046dfae96b687f2..59dbd2ba47dd747ace82d6a893ef3305fa675df4 100644 --- a/src/main/java/org/openepics/names/service/SubsystemService.java +++ b/src/main/java/org/openepics/names/service/SubsystemService.java @@ -28,9 +28,9 @@ import org.openepics.names.repository.ISubsystemRepository; import org.openepics.names.repository.SubsystemRepository; import org.openepics.names.repository.model.Subsystem; import org.openepics.names.rest.beans.Status; +import org.openepics.names.rest.beans.Type; import org.openepics.names.rest.beans.element.NameElement; import org.openepics.names.rest.beans.element.NameElementCommand; -import org.openepics.names.rest.beans.element.StructureElement; import org.openepics.names.rest.beans.element.StructureElementCommand; import org.openepics.names.util.HolderSystemDeviceStructure; import org.openepics.names.util.StructureChoice; @@ -38,6 +38,7 @@ import org.openepics.names.util.StructureCommand; import org.openepics.names.util.StructureElementUtil; import org.openepics.names.util.StructureUtil; import org.openepics.names.util.ValidateUtil; +import org.openepics.names.util.notification.NotificationUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; @@ -73,16 +74,16 @@ public class SubsystemService { } @Transactional(propagation = Propagation.MANDATORY) - public StructureElement deleteStructure(StructureElementCommand structureElement, - Date requested, String requestedBy) { + public StructureElementNotification deleteStructure(StructureElementCommand structureElement, + Date requested, String requestedBy, + HolderSystemDeviceStructure holder) { // validation outside method // transaction // do // find // create structure to pending, not latest, deleted, with data - // possibly validate that deleted // return - // structure element for deleted structure + // structure element for deleted structure + notification String uuid = structureElement.getUuid().toString(); @@ -100,11 +101,13 @@ public class SubsystemService { requested, requestedBy, structureElement.getComment()); subsystemRepository.createSubsystem(subsystem); - return StructureElementUtil.getStructureElementRequested(subsystem); + return new StructureElementNotification( + StructureElementUtil.getStructureElementRequested(subsystem), + NotificationUtil.prepareNotification(Type.SUBSYSTEM, StructureCommand.DELETE, null, null, subsystem, holder)); } @Transactional(propagation = Propagation.MANDATORY) - public StructureElement approveStructure(StructureElementCommand structureElement, + public StructureElementNotification approveStructure(StructureElementCommand structureElement, Date processed, String processedBy, HolderSystemDeviceStructure holder) { // validation outside method @@ -114,7 +117,6 @@ public class SubsystemService { // update not latest // find // approve - update structure to status APPROVED, latest to true - // possibly validate that approved // additional // find out previous to find out what is approved - create update delete // approve create @@ -131,7 +133,7 @@ public class SubsystemService { // keep related names - legacy names // delete sub structures // return - // structure element for approved structure + // structure element for approved structure + notification String uuid = structureElement.getUuid().toString(); String processedComment = structureElement.getComment(); @@ -159,16 +161,13 @@ public class SubsystemService { subsystem.setLatest(Boolean.TRUE); subsystemRepository.updateSubsystem(subsystem); - // additional - // find out previous + // previous List<Subsystem> previouses = iSubsystemRepository.findPreviousByUuidAndId(uuid, subsystem.getId()); StructureCommand structureCommandCUD = StructureUtil.getStructureCommandCUD(previouses, subsystem); Subsystem previous = previouses != null && !previouses.isEmpty() ? previouses.get(0) : null; LOGGER.log(Level.FINE, "approveStructure, structureCommandCUD: {0}", structureCommandCUD); - // approve create - // approve update - // approve delete + // additional if (StructureCommand.CREATE.equals(structureCommandCUD)) { boolean existsName = !StringUtils.isEmpty(subsystem.getMnemonic()) && namesService.existsName(StructureUtil.getMnemonicPath(subsystem, holder)); if (!existsName) { @@ -183,7 +182,9 @@ public class SubsystemService { } LOGGER.log(Level.FINE, "approveStructure, structureElement: {0}", structureElement); - return StructureElementUtil.getStructureElementProcessed(subsystem, holder, StructureChoice.STRUCTURE); + return new StructureElementNotification( + StructureElementUtil.getStructureElementProcessed(subsystem, holder, StructureChoice.STRUCTURE), + NotificationUtil.prepareNotification(Type.SUBSYSTEM, structureCommandCUD, StructureCommand.APPROVE, previous, subsystem, holder)); } } diff --git a/src/main/java/org/openepics/names/service/SystemGroupService.java b/src/main/java/org/openepics/names/service/SystemGroupService.java index 6ebabd1091e3025fec63b378d005eaa7e5bf945e..7ead3476975391713d41d2cdb51248b7e5478f25 100644 --- a/src/main/java/org/openepics/names/service/SystemGroupService.java +++ b/src/main/java/org/openepics/names/service/SystemGroupService.java @@ -33,7 +33,6 @@ import org.openepics.names.rest.beans.Status; import org.openepics.names.rest.beans.Type; import org.openepics.names.rest.beans.element.NameElement; import org.openepics.names.rest.beans.element.NameElementCommand; -import org.openepics.names.rest.beans.element.StructureElement; import org.openepics.names.rest.beans.element.StructureElementCommand; import org.openepics.names.util.HolderSystemDeviceStructure; import org.openepics.names.util.StructureChoice; @@ -41,6 +40,7 @@ import org.openepics.names.util.StructureCommand; import org.openepics.names.util.StructureElementUtil; import org.openepics.names.util.StructureUtil; import org.openepics.names.util.ValidateUtil; +import org.openepics.names.util.notification.NotificationUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; @@ -84,16 +84,16 @@ public class SystemGroupService { } @Transactional(propagation = Propagation.MANDATORY) - public StructureElement deleteStructure(StructureElementCommand structureElement, - Date requested, String requestedBy) { + public StructureElementNotification deleteStructure(StructureElementCommand structureElement, + Date requested, String requestedBy, + HolderSystemDeviceStructure holder) { // validation outside method // transaction // do // find // create structure to pending, not latest, deleted, with data - // possibly validate that deleted // return - // structure element for deleted structure + // structure element for deleted structure + notification String uuid = structureElement.getUuid().toString(); @@ -111,11 +111,13 @@ public class SystemGroupService { requested, requestedBy, structureElement.getComment()); systemGroupRepository.createSystemGroup(systemGroup); - return StructureElementUtil.getStructureElementRequested(systemGroup); + return new StructureElementNotification( + StructureElementUtil.getStructureElementRequested(systemGroup), + NotificationUtil.prepareNotification(Type.SYSTEMGROUP, StructureCommand.DELETE, null, null, systemGroup, holder)); } @Transactional(propagation = Propagation.MANDATORY) - public StructureElement approveStructure(StructureElementCommand structureElement, + public StructureElementNotification approveStructure(StructureElementCommand structureElement, Date processed, String processedBy, HolderSystemDeviceStructure holder) { // validation outside method @@ -125,7 +127,6 @@ public class SystemGroupService { // update not latest // find // approve - update structure to status APPROVED, latest to true - // possibly validate that approved // additional // find out previous to find out what is approved - create update delete // approve create @@ -142,7 +143,7 @@ public class SystemGroupService { // keep related names - legacy names // delete sub structures // return - // structure element for approved structure + // structure element for approved structure + notification String uuid = structureElement.getUuid().toString(); String processedComment = structureElement.getComment(); @@ -170,16 +171,13 @@ public class SystemGroupService { systemGroup.setLatest(Boolean.TRUE); systemGroupRepository.updateSystemGroup(systemGroup); - // additional - // find out previous + // previous List<SystemGroup> previouses = iSystemGroupRepository.findPreviousByUuidAndId(uuid, systemGroup.getId()); StructureCommand structureCommandCUD = StructureUtil.getStructureCommandCUD(previouses, systemGroup); SystemGroup previous = previouses != null && !previouses.isEmpty() ? previouses.get(0) : null; LOGGER.log(Level.INFO, "approveStructure, structureCommandCUD: {0}", structureCommandCUD); - // approve create - // approve update - // approve delete + // additional if (StructureCommand.CREATE.equals(structureCommandCUD)) { boolean existsName = !StringUtils.isEmpty(systemGroup.getMnemonic()) && namesService.existsName(StructureUtil.getMnemonicPath(systemGroup, holder)); if (!existsName) { @@ -198,14 +196,16 @@ public class SystemGroupService { structureElementCommands.add(new StructureElementCommand(system.getUuid(), Type.SYSTEM, null, null, null, null, StructuresService.DELETE_AFTER_APPROVE_STRUCTURE_CHANGE)); } for (StructureElementCommand structureElementCommand : structureElementCommands) { - systemService.deleteStructure(structureElementCommand, processed, processedBy); + systemService.deleteStructure(structureElementCommand, processed, processedBy, holder); } for (StructureElementCommand structureElementCommand : structureElementCommands) { systemService.approveStructure(structureElementCommand, processed, processedBy, holder); } } - return StructureElementUtil.getStructureElementProcessed(systemGroup, holder, StructureChoice.STRUCTURE); + return new StructureElementNotification( + StructureElementUtil.getStructureElementProcessed(systemGroup, holder, StructureChoice.STRUCTURE), + NotificationUtil.prepareNotification(Type.SYSTEMGROUP, structureCommandCUD, StructureCommand.APPROVE, previous, systemGroup, holder)); } } diff --git a/src/main/java/org/openepics/names/service/SystemService.java b/src/main/java/org/openepics/names/service/SystemService.java index 05ab5b078ac94dde257bd6cc07095e1346218c31..13535726e3f2ddd6ff55698c148aa24d591a28aa 100644 --- a/src/main/java/org/openepics/names/service/SystemService.java +++ b/src/main/java/org/openepics/names/service/SystemService.java @@ -33,7 +33,6 @@ import org.openepics.names.rest.beans.Status; import org.openepics.names.rest.beans.Type; import org.openepics.names.rest.beans.element.NameElement; import org.openepics.names.rest.beans.element.NameElementCommand; -import org.openepics.names.rest.beans.element.StructureElement; import org.openepics.names.rest.beans.element.StructureElementCommand; import org.openepics.names.util.HolderSystemDeviceStructure; import org.openepics.names.util.StructureChoice; @@ -41,6 +40,7 @@ import org.openepics.names.util.StructureCommand; import org.openepics.names.util.StructureElementUtil; import org.openepics.names.util.StructureUtil; import org.openepics.names.util.ValidateUtil; +import org.openepics.names.util.notification.NotificationUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; @@ -84,16 +84,16 @@ public class SystemService { } @Transactional(propagation = Propagation.MANDATORY) - public StructureElement deleteStructure(StructureElementCommand structureElement, - Date requested, String requestedBy) { + public StructureElementNotification deleteStructure(StructureElementCommand structureElement, + Date requested, String requestedBy, + HolderSystemDeviceStructure holder) { // validation outside method // transaction // do // find // create structure to pending, not latest, deleted, with data - // possibly validate that deleted // return - // structure element for deleted structure + // structure element for deleted structure + notification String uuid = structureElement.getUuid().toString(); @@ -111,11 +111,13 @@ public class SystemService { requested, requestedBy, structureElement.getComment()); systemRepository.createSystem(system); - return StructureElementUtil.getStructureElementRequested(system); + return new StructureElementNotification( + StructureElementUtil.getStructureElementRequested(system), + NotificationUtil.prepareNotification(Type.SYSTEM, StructureCommand.DELETE, null, null, system, holder)); } @Transactional(propagation = Propagation.MANDATORY) - public StructureElement approveStructure(StructureElementCommand structureElement, + public StructureElementNotification approveStructure(StructureElementCommand structureElement, Date processed, String processedBy, HolderSystemDeviceStructure holder) { // validation outside method @@ -125,7 +127,6 @@ public class SystemService { // update not latest // find // approve - update structure to status APPROVED, latest to true - // possibly validate that approved // additional // find out previous to find out what is approved - create update delete // approve create @@ -142,7 +143,7 @@ public class SystemService { // keep related names - legacy names // delete sub structures // return - // structure element for approved structure + // structure element for approved structure + notification String uuid = structureElement.getUuid().toString(); String processedComment = structureElement.getComment(); @@ -170,16 +171,13 @@ public class SystemService { system.setLatest(Boolean.TRUE); systemRepository.updateSystem(system); - // additional - // find out previous + // previous List<System> previouses = iSystemRepository.findPreviousByUuidAndId(uuid, system.getId()); StructureCommand structureCommandCUD = StructureUtil.getStructureCommandCUD(previouses, system); System previous = previouses != null && !previouses.isEmpty() ? previouses.get(0) : null; LOGGER.log(Level.FINE, "approveStructure, structureCommandCUD: {0}", structureCommandCUD); - // approve create - // approve update - // approve delete + // additional if (StructureCommand.CREATE.equals(structureCommandCUD)) { boolean existsName = !StringUtils.isEmpty(system.getMnemonic()) && namesService.existsName(StructureUtil.getMnemonicPath(system, holder)); if (!existsName) { @@ -198,7 +196,7 @@ public class SystemService { structureElementCommands.add(new StructureElementCommand(subsystem.getUuid(), Type.SUBSYSTEM, null, null, null, null, StructuresService.DELETE_AFTER_APPROVE_STRUCTURE_CHANGE)); } for (StructureElementCommand structureElementCommand : structureElementCommands) { - subsystemService.deleteStructure(structureElementCommand, processed, processedBy); + subsystemService.deleteStructure(structureElementCommand, processed, processedBy, holder); } for (StructureElementCommand structureElementCommand : structureElementCommands) { subsystemService.approveStructure(structureElementCommand, processed, processedBy, holder); @@ -206,7 +204,9 @@ public class SystemService { } LOGGER.log(Level.FINE, "approveStructure, structureElement: {0}", structureElement); - return StructureElementUtil.getStructureElementProcessed(system, holder, StructureChoice.STRUCTURE); + return new StructureElementNotification( + StructureElementUtil.getStructureElementProcessed(system, holder, StructureChoice.STRUCTURE), + NotificationUtil.prepareNotification(Type.SYSTEM, structureCommandCUD, StructureCommand.APPROVE, previous, system, holder)); } } diff --git a/src/main/java/org/openepics/names/util/notification/NotificationName.java b/src/main/java/org/openepics/names/util/notification/NotificationName.java new file mode 100644 index 0000000000000000000000000000000000000000..8a3afbf92d7a25108bde242dab43475961ff7d55 --- /dev/null +++ b/src/main/java/org/openepics/names/util/notification/NotificationName.java @@ -0,0 +1,115 @@ +/* + * 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.util.notification; + +import java.util.Date; +import java.util.UUID; + +import org.openepics.names.util.NameCommand; + +/** + * Utility class to store data for user notification of changes for name in Naming. + * + * @author Lars Johansson + */ +public class NotificationName { + + // cud - create update delete + + private NameCommand nameCommandCUD; + private UUID uuid; + private String oldIndex; + private String oldName; + private String oldDescription; + private String newIndex; + private String newName; + private String newDescription; + private String comment; + private Date when; + private String who; + + /** + * Public constructor. + * + * @param nameCommandCUD name command (create update delete) + * @param uuid uuid + * @param oldIndex old index + * @param oldName old name + * @param oldDescription old description + * @param newIndex new index + * @param newName new name + * @param newDescription new description + * @param comment comment + * @param when when + * @param who who + */ + public NotificationName(NameCommand nameCommandCUD, + UUID uuid, + String oldIndex, String oldName, String oldDescription, + String newIndex, String newName, String newDescription, + String comment, Date when, String who) { + super(); + this.nameCommandCUD = nameCommandCUD; + this.uuid = uuid; + this.oldIndex = oldIndex; + this.oldName = oldName; + this.oldDescription = oldDescription; + this.newIndex = newIndex; + this.newName = newName; + this.newDescription = newDescription; + this.comment = comment; + this.when = when; + this.who = who; + } + + public NameCommand getNameCommandCUD() { + return nameCommandCUD; + } + public UUID getUuid() { + return uuid; + } + public String getOldIndex() { + return oldIndex; + } + public String getOldName() { + return oldName; + } + public String getOldDescription() { + return oldDescription; + } + public String getNewIndex() { + return newIndex; + } + public String getNewName() { + return newName; + } + public String getNewDescription() { + return newDescription; + } + public String getComment() { + return comment; + } + public Date getWhen() { + return when; + } + public String getWho() { + return who; + } + +} diff --git a/src/main/java/org/openepics/names/util/notification/NotificationScheduler.java b/src/main/java/org/openepics/names/util/notification/NotificationScheduler.java new file mode 100644 index 0000000000000000000000000000000000000000..5ccb428e8a64fc84a6afdfd7a4a55a7eb8b54e14 --- /dev/null +++ b/src/main/java/org/openepics/names/util/notification/NotificationScheduler.java @@ -0,0 +1,251 @@ +/* + * 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.util.notification; + +import java.text.MessageFormat; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.openepics.names.repository.INameRepository; +import org.openepics.names.repository.model.Name; +import org.openepics.names.service.NotificationService; +import org.openepics.names.util.NameCommand; +import org.openepics.names.util.ValidateUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import com.google.common.collect.Lists; + +/** + * Scheduler class to notify users and administrators about changes for names and structures in Naming. + * + * @author Lars Johansson + */ +@Component +@EnableScheduling +public class NotificationScheduler { + + private static final Logger LOGGER = Logger.getLogger(NotificationScheduler.class.getName()); + + private static final String NOTIFY_NAMES_FROM_TO_CREATED_UPDATED_DELETED = "Notify names, {0} - {1}, # created: {2}, # updated: {3}, # deleted: {4}"; + + @Autowired + private INameRepository iNameRepository; + @Autowired + private NotificationService notificationService; + + /** + * Notify administrators about changes for names and structures. + * Intention is to inform about changes for names (create, update, delete). + * It is to be checked once a day, and notifications to be generated with emails. + * Emails is to have limited size of entries and another email is to be generated with if size is greater than limit. + * + * <br/><br/> + * Scheduling is handled with cron expression. + */ + @Scheduled(cron = "0 0 5 * * *") + public void notifyAdministrators() { + notifyAdministratorsForNames(); + } + + /** + * Find out which names that are created, updated or deleted the previous day + * and send notification to administrators for those names. + */ + private void notifyAdministratorsForNames() { + // prepare + // find names for previous day + // find changes for names + // split into create, update, delete + // prepare notification + // log + // send notification + // one email with sections for create, update, delete + + // prepare + // dates for previous day + // created + // updated + // deleted + Date from = getPreviousDayFrom(); + Date to = getPreviousDayTo(); + List<NotificationName> created = Lists.newArrayList(); + List<NotificationName> updated = Lists.newArrayList(); + List<NotificationName> deleted = Lists.newArrayList(); + + // find names for previous day + // uuid ascending, id ascending + // distinct uuids + List<Name> names = iNameRepository.findByRequestedBetween(from, to); + Set<UUID> uuids = new TreeSet<>(); + for (Name name : names) { + uuids.add(name.getUuid()); + } + + // find changes for names + // (possibly one notification for uuid) + // per uuid + // prepare + // extract names per uuid (previous day) + // get names by uuid + // sort by id ascending + // step in list until same id + // compare + // extracted names to names by uuid + // split into + // created + // updated + // deleted + for (UUID uuid : uuids) { + // prepare + List<Name> extracted = extractByUuid(names, uuid); + List<Name> namesByUuid = iNameRepository.findByUuid(uuid.toString()); + sortById(extracted); + sortById(namesByUuid); + + int index = -1; + for (int i=0; i<namesByUuid.size(); i++) { + if (extracted.get(0).getId().equals(namesByUuid.get(0).getId())) { + index = i; + break; + } + } + + // compare + for (int i=0; i<extracted.size(); i++) { + // created - no previous + // updated - is not latest + // deleted - is latest and is deleted + Name name = extracted.get(i); + if (i == 0 && index == 0) { + NotificationUtil.prepareAddNotification(created, NameCommand.CREATE, null, name); + } else if (Boolean.TRUE.equals(name.isLatest()) && Boolean.TRUE.equals(name.isDeleted())) { + NotificationUtil.prepareAddNotification(deleted, NameCommand.DELETE, null, name); + } else { + NotificationUtil.prepareAddNotification(updated, NameCommand.UPDATE, findPreviousByName(namesByUuid, name), name); + } + } + } + + // log + LOGGER.log(Level.INFO, + () -> MessageFormat.format( + NOTIFY_NAMES_FROM_TO_CREATED_UPDATED_DELETED, + from.toString(), + to.toString(), + created.size(), + updated.size(), + deleted.size())); + + // send notification + notificationService.notifyNames(created, updated, deleted); + } + + /** + * Return date for beginning of previous day. + * + * @return date date for beginning of previous day + */ + private Date getPreviousDayFrom() { + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DAY_OF_YEAR, -1); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTime(); + } + + /** + * Return date for end of previous day. + * + * @return date for end of previous day + */ + private Date getPreviousDayTo() { + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DAY_OF_YEAR, -1); + cal.set(Calendar.HOUR_OF_DAY, 23); + cal.set(Calendar.MINUTE, 59); + cal.set(Calendar.SECOND, 59); + cal.set(Calendar.MILLISECOND, 999); + return cal.getTime(); + } + + /** + * Extract names that contain given uuid from list of names. + * + * @param list list of names + * @param uuid uuid + * @return list of names + */ + private List<Name> extractByUuid(List<Name> list, UUID uuid) { + List<Name> extracted = Lists.newArrayList(); + if (list != null && !list.isEmpty() && uuid != null) { + for (Name name : list) { + if (uuid.equals(name.getUuid())) { + extracted.add(name); + } + } + } + return extracted; + } + + /** + * Utility method to sort names according to id ascending. + * + * @param names names + */ + private void sortById(List<Name> names) { + Collections.sort(names, (Name arg0, Name arg1) -> arg0.getId().compareTo(arg1.getId())); + } + + /** + * Return name previous to given name in list. + * + * @param names list of names + * @param name name + * @return name + */ + private Name findPreviousByName(List<Name> names, Name name) { + if (ValidateUtil.isAnyNull(names, name)) { + return null; + } + + // sorted list + for (int i=0; i<names.size(); i++) { + if (name.getId().equals(names.get(i).getId())) { + return i > 0 + ? names.get(i-1) + : null; + } + } + + return null; + } + +} diff --git a/src/main/java/org/openepics/names/util/notification/NotificationStructure.java b/src/main/java/org/openepics/names/util/notification/NotificationStructure.java new file mode 100644 index 0000000000000000000000000000000000000000..ffaf105108b132077042f736392e6fcaaca1b38f --- /dev/null +++ b/src/main/java/org/openepics/names/util/notification/NotificationStructure.java @@ -0,0 +1,141 @@ +/* + * 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.util.notification; + +import java.util.Date; +import java.util.UUID; + +import org.openepics.names.rest.beans.Type; +import org.openepics.names.util.StructureCommand; + +/** + * Utility class to store data for user notification of change for structure in Naming. + * + * @author Lars Johansson + */ +public class NotificationStructure { + + // cud - create update delete + // acr - approve cancel reject + + private StructureCommand structureCommandCUD; + private StructureCommand structureCommandACR; + private UUID uuid; + private Type type; + private String oldName; + private String oldMnemonic; + private String oldMnemonicpath; + private String oldDescription; + private String newName; + private String newMnemonic; + private String newMnemonicpath; + private String newDescription; + private String comment; + private Date when; + private String who; + + /** + * Public constructor. + * + * @param structureCommandCUD structure command (create update delete) + * @param structureCommandACR structure command (approve cancel reject) + * @param type type + * @param uuid uuid + * @param oldName old name + * @param oldMnemonic old mnemonic + * @param oldMnemonicpath old mnemonic path + * @param oldDescription old description + * @param newName new name + * @param newMnemonic new mnemonic + * @param newMnemonicpath new mnemonic path + * @param newDescription new description + * @param comment comment + * @param when when + * @param who who + */ + public NotificationStructure(StructureCommand structureCommandCUD, StructureCommand structureCommandACR, + Type type, UUID uuid, + String oldName, String oldMnemonic, String oldMnemonicpath, String oldDescription, + String newName, String newMnemonic, String newMnemonicpath, String newDescription, + String comment, Date when, String who) { + super(); + this.structureCommandCUD = structureCommandCUD; + this.structureCommandACR = structureCommandACR; + this.type = type; + this.uuid = uuid; + this.oldName = oldName; + this.oldMnemonic = oldMnemonic; + this.oldMnemonicpath = oldMnemonicpath; + this.oldDescription = oldDescription; + this.newName = newName; + this.newMnemonic = newMnemonic; + this.newMnemonicpath = newMnemonicpath; + this.newDescription = newDescription; + this.comment = comment; + this.when = when; + this.who = who; + } + + public StructureCommand getStructureCommandCUD() { + return structureCommandCUD; + } + public StructureCommand getStructureCommandACR() { + return structureCommandACR; + } + public Type getType() { + return type; + } + public UUID getUuid() { + return uuid; + } + public String getOldName() { + return oldName; + } + public String getOldMnemonic() { + return oldMnemonic; + } + public String getOldMnemonicpath() { + return oldMnemonicpath; + } + public String getOldDescription() { + return oldDescription; + } + public String getNewName() { + return newName; + } + public String getNewMnemonic() { + return newMnemonic; + } + public String getNewMnemonicpath() { + return newMnemonicpath; + } + public String getNewDescription() { + return newDescription; + } + public String getComment() { + return comment; + } + public Date getWhen() { + return when; + } + public String getWho() { + return who; + } + +} diff --git a/src/main/java/org/openepics/names/util/notification/NotificationUtil.java b/src/main/java/org/openepics/names/util/notification/NotificationUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..4b1d95d43a18740fe7481ea3ca705dd618cc6ff4 --- /dev/null +++ b/src/main/java/org/openepics/names/util/notification/NotificationUtil.java @@ -0,0 +1,352 @@ +/* + * 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.util.notification; + +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang3.StringUtils; +import org.openepics.names.repository.model.DeviceGroup; +import org.openepics.names.repository.model.DeviceType; +import org.openepics.names.repository.model.Discipline; +import org.openepics.names.repository.model.Name; +import org.openepics.names.repository.model.Structure; +import org.openepics.names.repository.model.Subsystem; +import org.openepics.names.repository.model.System; +import org.openepics.names.repository.model.SystemGroup; +import org.openepics.names.rest.beans.Type; +import org.openepics.names.util.HolderSystemDeviceStructure; +import org.openepics.names.util.NameCommand; +import org.openepics.names.util.StructureCommand; +import org.openepics.names.util.StructureUtil; +import org.openepics.names.util.ValidateUtil; + +/** + * Utility class to assist in handling of notifications. + * + * @author Lars Johansson + */ +public class NotificationUtil { + + private static final Logger LOGGER = Logger.getLogger(NotificationUtil.class.getName()); + + private static final String EMPTY = ""; + + /** + * This class is not to be instantiated. + */ + private NotificationUtil() { + throw new IllegalStateException("Utility class"); + } + + /** + * Highly specialized method to find out kind of structure command (create, update, delete) for notification purposes. + * Intended to be used for notification purposes in approve, cancel, reject operations. + * + * @param previous list of previous structures + * @param structure structure + * @return structure command + */ + public static StructureCommand getStructureCommandCUD(List<?> previous, Structure structure) { + if (previous == null || previous.isEmpty()) { + return StructureCommand.CREATE; + } else if (structure != null && Boolean.TRUE.equals(structure.isDeleted())) { + return StructureCommand.DELETE; + } else { + return StructureCommand.UPDATE; + } + } + + /** + * Prepare notification and add to list of notifications if notification is available. + * + * @param notifications list of notifications + * @param nameCommand name command + * @param previous previous (name) + * @param name name + */ + public static void prepareAddNotification(List<NotificationName> notifications, NameCommand nameCommand, Name previous, Name name) { + if (notifications == null) { + return; + } + + NotificationName notification = NotificationUtil.prepareNotification(nameCommand, previous, name); + if (notification != null) { + notifications.add(notification); + } + } + + /** + * Prepare and return notification for name with information about change. + * If information is not correct, <tt>null</tt> is returned. + * + * @param nameCommand name command + * @param previous previous (name) + * @param name name + * @return notification + */ + public static NotificationName prepareNotification(NameCommand nameCommand, Name previous, Name name) { + LOGGER.log(Level.FINE, "prepareNotification, nameCommand: {0}", nameCommand); + LOGGER.log(Level.FINE, "prepareNotification, previous: {0}", previous); + LOGGER.log(Level.FINE, "prepareNotification, name: {0}", name); + + if (ValidateUtil.isAnyNull(nameCommand, name)) { + return null; + } + if (ValidateUtil.isAnyEqual(nameCommand, NameCommand.UPDATE) && previous == null) { + return null; + } + + // notify + // combinations of commands and names + // about information going from previous to present + // to be able to notify about change + // ------------------------------------------------ + // create name + // update previous + name + // delete name + // support fields + // name + // description + + String oldIndex = EMPTY; + String oldName = EMPTY; + String oldDescription = EMPTY; + String newIndex = StringUtils.stripToEmpty(name.getInstanceIndex()); + String newName = StringUtils.stripToEmpty(name.getConventionName()); + String newDescription = StringUtils.stripToEmpty(name.getDescription()); + + if (ValidateUtil.isAnyEqual(nameCommand, NameCommand.UPDATE)) { + oldIndex = StringUtils.stripToEmpty(previous.getInstanceIndex()); + oldName = StringUtils.stripToEmpty(previous.getConventionName()); + oldDescription = StringUtils.stripToEmpty(previous.getDescription()); + } + + return new NotificationName( + nameCommand, + name.getUuid(), + oldIndex, + oldName, + oldDescription, + newIndex, + newName, + newDescription, + name.getRequestedComment(), + name.getRequested(), + name.getRequestedBy()); + } + + /** + * Prepare notification and add to list of notifications if notification is available. + * + * @param notifications list of notifications + * @param type type + * @param structureCommandCUD structure command (create update delete) + * @param structureCommandACR structure command (approve cancel reject) + * @param previous previous (structure) + * @param structure structure + * @param holderSystemDeviceStructure holder of containers for system and device structure content + */ + public static void prepareAddNotification(List<NotificationStructure> notifications, + Type type, StructureCommand structureCommandCUD, StructureCommand structureCommandACR, + Structure previous, Structure structure, HolderSystemDeviceStructure holderSystemDeviceStructure) { + if (notifications == null) { + return; + } + + NotificationStructure notification = NotificationUtil.prepareNotification(type, structureCommandCUD, structureCommandACR, previous, structure, holderSystemDeviceStructure); + if (notification != null) { + notifications.add(notification); + } + } + + /** + * Prepare and return notification for system group with information about change. + * If information is not correct, <tt>null</tt> is returned. + * + * @param type type + * @param structureCommandCUD structure command (create update delete) + * @param structureCommandACR structure command (approve cancel reject) + * @param previous previous (structure) + * @param structure structure + * @param holderSystemDeviceStructure holder of containers for system and device structure content + * @return notification + */ + public static NotificationStructure prepareNotification(Type type, StructureCommand structureCommandCUD, StructureCommand structureCommandACR, + Structure previous, Structure structure, HolderSystemDeviceStructure holderSystemDeviceStructure) { + LOGGER.log(Level.FINE, "prepareNotification, type: {0}", type); + LOGGER.log(Level.FINE, "prepareNotification, structureCommandCUD: {0}", structureCommandCUD); + LOGGER.log(Level.FINE, "prepareNotification, structureCommandACR: {0}", structureCommandACR); + LOGGER.log(Level.FINE, "prepareNotification, previous: {0}", previous); + LOGGER.log(Level.FINE, "prepareNotification, structure: {0}", structure); + + if (ValidateUtil.isAnyNull(holderSystemDeviceStructure, type, structureCommandCUD, structure)) { + return null; + } + if (ValidateUtil.isAnyEqual(structureCommandCUD, StructureCommand.APPROVE, StructureCommand.CANCEL, StructureCommand.REJECT)) { + return null; + } + if (ValidateUtil.isAnyEqual(structureCommandACR, StructureCommand.CREATE, StructureCommand.UPDATE, StructureCommand.DELETE)) { + return null; + } + if (ValidateUtil.isAnyEqual(structureCommandCUD, StructureCommand.UPDATE) && previous == null) { + return null; + } + + // type + // system group + // system + // subsystem + // discipline + // device group + // device type + // notify - structureCommandCUD + structureCommandACR + // combinations of commands and structures + // about information going from previous to present + // to be able to notify about change + // ------------------------------------------------ + // create structure + // create + approve structure + // create + cancel structure + // create + reject structure + // update previous + structure + // update + approve previous + structure + // update + cancel previous + structure + // update + reject previous + structure + // delete structure + // delete + approve structure + // delete + cancel structure + // delete + reject structure + // support fields + // name + // mnemonic + // mnemonic path - to support hierarchy + // description + + String oldName = EMPTY; + String oldMnemonic = EMPTY; + String oldMnemonicpath = EMPTY; + String oldDescription = EMPTY; + String newName = EMPTY; + String newMnemonic = EMPTY; + String newMnemonicpath = EMPTY; + String newDescription = EMPTY; + + String comment = EMPTY; + Date when = null; + String who = EMPTY; + + if (structureCommandACR == null) { + comment = structure.getRequestedComment(); + when = structure.getRequested(); + who = structure.getRequestedBy(); + } else { + comment = structure.getProcessedComment(); + when = structure.getProcessed(); + who = structure.getProcessedBy(); + } + + comment = StringUtils.stripToEmpty(comment); + who = StringUtils.stripToEmpty(who); + + if (ValidateUtil.isAnyEqual(structureCommandCUD, StructureCommand.UPDATE)) { + if (Type.SYSTEMGROUP.equals(type)) { + oldMnemonicpath = StructureUtil.getMnemonicPath((SystemGroup) previous, holderSystemDeviceStructure); + } else if (Type.SYSTEM.equals(type)) { + oldMnemonicpath = StructureUtil.getMnemonicPath((System) previous, holderSystemDeviceStructure); + } else if (Type.SUBSYSTEM.equals(type)) { + oldMnemonicpath = StructureUtil.getMnemonicPath((Subsystem) previous, holderSystemDeviceStructure); + } else if (Type.DISCIPLINE.equals(type)) { + oldMnemonicpath = StructureUtil.getMnemonicPath((Discipline) previous, holderSystemDeviceStructure); + } else if (Type.DEVICEGROUP.equals(type)) { + oldMnemonicpath = StructureUtil.getMnemonicPath((DeviceGroup) previous, holderSystemDeviceStructure); + } else if (Type.DEVICETYPE.equals(type)) { + oldMnemonicpath = StructureUtil.getMnemonicPath((DeviceType) previous, holderSystemDeviceStructure); + } + + oldName = StringUtils.stripToEmpty(previous.getName()); + oldMnemonic = StringUtils.stripToEmpty(previous.getMnemonic()); + oldMnemonicpath = StringUtils.stripToEmpty(oldMnemonicpath); + oldDescription = StringUtils.stripToEmpty(previous.getDescription()); + } + + if (Type.SYSTEMGROUP.equals(type)) { + newMnemonicpath = StructureUtil.getMnemonicPath((SystemGroup) structure, holderSystemDeviceStructure); + } else if (Type.SYSTEM.equals(type)) { + newMnemonicpath = StructureUtil.getMnemonicPath((System) structure, holderSystemDeviceStructure); + } else if (Type.SUBSYSTEM.equals(type)) { + newMnemonicpath = StructureUtil.getMnemonicPath((Subsystem) structure, holderSystemDeviceStructure); + } else if (Type.DISCIPLINE.equals(type)) { + newMnemonicpath = StructureUtil.getMnemonicPath((Discipline) structure, holderSystemDeviceStructure); + } else if (Type.DEVICEGROUP.equals(type)) { + newMnemonicpath = StructureUtil.getMnemonicPath((DeviceGroup) structure, holderSystemDeviceStructure); + } else if (Type.DEVICETYPE.equals(type)) { + newMnemonicpath = StructureUtil.getMnemonicPath((DeviceType) structure, holderSystemDeviceStructure); + } + newName = StringUtils.stripToEmpty(structure.getName()); + newMnemonic = StringUtils.stripToEmpty(structure.getMnemonic()); + newMnemonicpath = StringUtils.stripToEmpty(newMnemonicpath); + newDescription = StringUtils.stripToEmpty(structure.getDescription()); + + return new NotificationStructure( + structureCommandCUD, + structureCommandACR, + type, + structure.getUuid(), + oldName, + oldMnemonic, + oldMnemonicpath, + oldDescription, + newName, + newMnemonic, + newMnemonicpath, + newDescription, + comment, + when, + who); + } + + /** + * Utility method to sort name notifications according to new name ascending. + * + * @param notifications name notifications + */ + public static void sortByNewName(List<NotificationName> notifications) { + if (notifications != null) { + Collections.sort(notifications, + (NotificationName arg0, NotificationName arg1) -> arg0.getNewName().compareTo(arg1.getNewName())); + } + } + + /** + * Utility method to sort structure notifications according to new mnemonic path ascending. + * + * @param notifications structure notifications + */ + public static void sortByNewMnemonicpath(List<NotificationStructure> notifications) { + if (notifications != null) { + Collections.sort(notifications, + (NotificationStructure arg0, NotificationStructure arg1) -> arg0.getNewMnemonicpath().compareTo(arg1.getNewMnemonicpath())); + } + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ab1cafdc96184096987e82a3b19c600dc4f81bf9..0ecb45c61bc8fadced08099a3009ea6530e09dad 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,6 +12,8 @@ logging.level.org.springframework.web=INFO spring.http.log-request-details=true # mail +# administrator mail is comma-separated list of email addresses +naming.mail.administrator=${NAMING_MAIL_ADMINISTRATOR:} 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} diff --git a/src/main/resources/templates/notification_names.html b/src/main/resources/templates/notification_names.html new file mode 100644 index 0000000000000000000000000000000000000000..db809bdb2e89553996ac73054522e9b0783b4b14 --- /dev/null +++ b/src/main/resources/templates/notification_names.html @@ -0,0 +1,200 @@ +<!DOCTYPE html> +<html xmlns:th="http://www.thymeleaf.org" xmlns:a="http://www.w3.org/1999/html"> + <head> + <title th:remove="all">There are changes in Naming for names</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <style> + th, td { + border-bottom: 1px solid #ddd; + padding-left: 20px; + text-align:left; + } + th { + background-color: #0094ca; + color: #FFFFFF; + } + table { + border-collapse: collapse; + } + thead { + border: 0; + } + </style> + </head> + <body> + <h2>There are changes in Naming for names</h2> + <br/> + + <table> + <tr th:if="${numberCreated > 0}"> + <td style="padding-left: 0px;">Number of created names: </td> + <td th:text="${numberCreated}"/> + </tr> + <tr th:if="${numberUpdated > 0}"> + <td style="padding-left: 0px;">Number of updated names: </td> + <td th:text="${numberUpdated}"/> + </tr> + <tr th:if="${numberDeleted > 0}"> + <td style="padding-left: 0px;">Number of deleted names: </td> + <td th:text="${numberDeleted}"/> + </tr> + </table> + <br/> + + <div th:if="${numberCreated > 0}"> + <div style="padding-bottom: 10px;"> + <h3>Created</h3> + </div> + + <table style="padding-bottom: 10px; width: 90%;"> + <thead> + <tr> + <th>Create Update Delete</th> + + <th>Old</th> + <th/> + <th>New</th> + + <th>Comment</th> + <th>When</th> + <th>Who</th> + </tr> + </thead> + <tbody> + <tr th:each="row: ${created}"> + <td th:text="${row.getNameCommandCUD()}"/> + + <td style="font-weight: bold;"> + <div> + <div th:text=${row.getOldIndex()}></div> + <div th:text=${row.getOldName()}></div> + <div th:text=${row.getOldDescription()}></div> + </div> + </td> + <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/> + <td style="font-weight: bold;"> + <div> + <div th:text=${row.getNewIndex()}></div> + <div th:text=${row.getNewName()}></div> + <div th:text=${row.getNewDescription()}></div> + </div> + </td> + + <td th:text="${row.getComment()}"/> + <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/> + <td th:text="${row.getWho()}"/> + </tr> + </tbody> + </table> + <br/> + <hr> + </div> + + <div th:if="${numberUpdated > 0}"> + <div style="padding-bottom: 10px;"> + <h3>Updated</h3> + </div> + + <table style="padding-bottom: 10px; width: 90%;"> + <thead> + <tr> + <th>Create Update Delete</th> + + <th>Old</th> + <th/> + <th>New</th> + + <th>Comment</th> + <th>When</th> + <th>Who</th> + </tr> + </thead> + <tbody> + <tr th:each="row: ${updated}"> + <td th:text="${row.getNameCommandCUD()}"/> + + <td style="font-weight: bold;"> + <div> + <div style="text-align: left;" th:text=${row.getOldIndex()}></div> + <div style="text-align: left;" th:text=${row.getOldName()}></div> + <div style="text-align: left;" th:text=${row.getOldDescription()}></div> + </div> + </td> + <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/> + <td style="font-weight: bold;"> + <div> + <div style="text-align: left;" th:text=${row.getNewIndex()}></div> + <div style="text-align: left;" th:text=${row.getNewName()}></div> + <div style="text-align: left;" th:text=${row.getNewDescription()}></div> + </div> + </td> + + <td th:text="${row.getComment()}"/> + <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/> + <td th:text="${row.getWho()}"/> + </tr> + </tbody> + </table> + <br/> + <hr> + </div> + + <div th:if="${numberDeleted > 0}"> + <div style="padding-bottom: 10px;"> + <h3>Deleted</h3> + </div> + + <table style="padding-bottom: 10px; width: 90%;"> + <thead> + <tr> + <th>Create Update Delete</th> + + <th>Old</th> + <th/> + <th>New</th> + + <th>Comment</th> + <th>When</th> + <th>Who</th> + </tr> + </thead> + <tbody> + <tr th:each="row: ${deleted}"> + <td th:text="${row.getNameCommandCUD()}"/> + + <td style="font-weight: bold;"> + <div> + <div style="text-align: left;" th:text=${row.getOldIndex()}></div> + <div style="text-align: left;" th:text=${row.getOldName()}></div> + <div style="text-align: left;" th:text=${row.getOldDescription()}></div> + </div> + </td> + <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/> + <td style="font-weight: bold;"> + <div> + <div style="text-align: left;" th:text=${row.getNewIndex()}></div> + <div style="text-align: left;" th:text=${row.getNewName()}></div> + <div style="text-align: left;" th:text=${row.getNewDescription()}></div> + </div> + </td> + + <td th:text="${row.getComment()}"/> + <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/> + <td th:text="${row.getWho()}"/> + </tr> + </tbody> + </table> + <br/> + <hr> + </div> + + <div th:if="${addfooter}"> + <div th:text="'Notifications show old and new values for Index, Name, Description.'"></div> + <br/> + <hr> + + <div th:text="'Naming URL:'" style="display: inline;"></div> + <a th:href="@{${backendurl}}" th:text="${backendurl}"/></a> + </div> + </body> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/notification_structures.html b/src/main/resources/templates/notification_structures.html new file mode 100644 index 0000000000000000000000000000000000000000..434b1a5aba5e3ff68acee0971e0a6512a7bafb4e --- /dev/null +++ b/src/main/resources/templates/notification_structures.html @@ -0,0 +1,400 @@ +<!DOCTYPE html> +<html xmlns:th="http://www.thymeleaf.org" xmlns:a="http://www.w3.org/1999/html"> + <head> + <title th:remove="all">There are changes in Naming for structures</title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <style> + th, td { + border-bottom: 1px solid #ddd; + padding-left: 20px; + text-align:left; + } + th { + background-color: #0094ca; + color: #FFFFFF; + } + table { + border-collapse: collapse; + } + thead { + border: 0; + } + </style> + </head> + <body> + + <h2>There are changes in Naming for structures</h2> + <br/> + + <table> + <tr th:if="${numberCreated > 0}"> + <td style="padding-left: 0px;">Number of created structures: </td> + <td th:text="${numberCreated}"/> + <td>that need APPROVE, CANCEL, REJECT</td> + </tr> + <tr th:if="${numberUpdated > 0}"> + <td style="padding-left: 0px;">Number of updated structures: </td> + <td th:text="${numberUpdated}"/> + <td>that need APPROVE, CANCEL, REJECT</td> + </tr> + <tr th:if="${numberDeleted > 0}"> + <td style="padding-left: 0px;">Number of deleted structures: </td> + <td th:text="${numberDeleted}"/> + <td>that need APPROVE, CANCEL, REJECT</td> + </tr> + <tr th:if="${numberApproved > 0}"> + <td style="padding-left: 0px;">Number of approved structures: </td> + <td th:text="${numberApproved}"/> + </tr> + <tr th:if="${numberCancelled > 0}"> + <td style="padding-left: 0px;">Number of cancelled structures: </td> + <td th:text="${numberCancelled}"/> + </tr> + <tr th:if="${numberRejected > 0}"> + <td style="padding-left: 0px;">Number of rejected structures: </td> + <td th:text="${numberRejected}"/> + </tr> + </table> + <br/> + + <div th:if="${numberCreated > 0}"> + <div style="padding-bottom: 10px;"> + <h3>Created</h3> + </div> + + <table style="padding-bottom: 10px; width: 90%"> + <thead> + <tr> + <th>Type</th> + <th>Create Update Delete</th> + <th>Approve Cancel Reject</th> + + <th>Old</th> + <th/> + <th>New</th> + + <th>Comment</th> + <th>When</th> + <th>Who</th> + </tr> + </thead> + <tbody> + <tr th:each="row: ${created}"> + <td th:text="${row.getType()}"/> + <td th:text="${row.getStructureCommandCUD()}"/> + <td th:text="${row.getStructureCommandACR()}"/> + + <td style="font-weight: bold;"> + <div> + <div th:text=${row.getOldName()}></div> + <div th:text=${row.getOldMnemonic()}></div> + <div th:text=${row.getOldMnemonicpath()}></div> + <div th:text=${row.getOldDescription()}></div> + </div> + </td> + <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/> + <td style="font-weight: bold;"> + <div> + <div th:text=${row.getNewName()}></div> + <div th:text=${row.getNewMnemonic()}></div> + <div th:text=${row.getNewMnemonicpath()}></div> + <div th:text=${row.getNewDescription()}></div> + </div> + </td> + + <td th:text="${row.getComment()}"/> + <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/> + <td th:text="${row.getWho()}"/> + </tr> + </tbody> + </table> + <br/> + <hr> + </div> + + <div th:if="${numberUpdated > 0}"> + <div style="padding-bottom: 10px;"> + <h3>Updated</h3> + </div> + + <table style="padding-bottom: 10px; width: 90%"> + <thead> + <tr> + <th>Type</th> + <th>Create Update Delete</th> + <th>Approve Cancel Reject</th> + + <th>Old</th> + <th/> + <th>New</th> + + <th>Comment</th> + <th>When</th> + <th>Who</th> + </tr> + </thead> + <tbody> + <tr th:each="row: ${updated}"> + <td th:text="${row.getType()}"/> + <td th:text="${row.getStructureCommandCUD()}"/> + <td th:text="${row.getStructureCommandACR()}"/> + + <td style="font-weight: bold;"> + <div> + <div th:text=${row.getOldName()}></div> + <div th:text=${row.getOldMnemonic()}></div> + <div th:text=${row.getOldMnemonicpath()}></div> + <div th:text=${row.getOldDescription()}></div> + </div> + </td> + <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/> + <td style="font-weight: bold;"> + <div> + <div th:text=${row.getNewName()}></div> + <div th:text=${row.getNewMnemonic()}></div> + <div th:text=${row.getNewMnemonicpath()}></div> + <div th:text=${row.getNewDescription()}></div> + </div> + </td> + + <td th:text="${row.getComment()}"/> + <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/> + <td th:text="${row.getWho()}"/> + </tr> + </tbody> + </table> + <br/> + <hr> + </div> + + <div th:if="${numberDeleted > 0}"> + <div style="padding-bottom: 10px;"> + <h3>Deleted</h3> + </div> + + <table style="padding-bottom: 10px; width: 90%"> + <thead> + <tr> + <th>Type</th> + <th>Create Update Delete</th> + <th>Approve Cancel Reject</th> + + <th>Old</th> + <th/> + <th>New</th> + + <th>Comment</th> + <th>When</th> + <th>Who</th> + </tr> + </thead> + <tbody> + <tr th:each="row: ${deleted}"> + <td th:text="${row.getType()}"/> + <td th:text="${row.getStructureCommandCUD()}"/> + <td th:text="${row.getStructureCommandACR()}"/> + + <td style="font-weight: bold;"> + <div> + <div th:text=${row.getOldName()}></div> + <div th:text=${row.getOldMnemonic()}></div> + <div th:text=${row.getOldMnemonicpath()}></div> + <div th:text=${row.getOldDescription()}></div> + </div> + </td> + <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/> + <td style="font-weight: bold;"> + <div> + <div th:text=${row.getNewName()}></div> + <div th:text=${row.getNewMnemonic()}></div> + <div th:text=${row.getNewMnemonicpath()}></div> + <div th:text=${row.getNewDescription()}></div> + </div> + </td> + + <td th:text="${row.getComment()}"/> + <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/> + <td th:text="${row.getWho()}"/> + </tr> + </tbody> + </table> + <br/> + <hr> + </div> + + <div th:if="${numberApproved > 0}"> + <div style="padding-bottom: 10px;"> + <h3>Approved</h3> + </div> + + <table style="padding-bottom: 10px; width: 90%"> + <thead> + <tr> + <th>Type</th> + <th>Create Update Delete</th> + <th>Approve Cancel Reject</th> + + <th>Old</th> + <th/> + <th>New</th> + + <th>Comment</th> + <th>When</th> + <th>Who</th> + </tr> + </thead> + <tbody> + <tr th:each="row: ${approved}"> + <td th:text="${row.getType()}"/> + <td th:text="${row.getStructureCommandCUD()}"/> + <td th:text="${row.getStructureCommandACR()}"/> + + <td style="font-weight: bold;"> + <div> + <div th:text=${row.getOldName()}></div> + <div th:text=${row.getOldMnemonic()}></div> + <div th:text=${row.getOldMnemonicpath()}></div> + <div th:text=${row.getOldDescription()}></div> + </div> + </td> + <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/> + <td style="font-weight: bold;"> + <div> + <div th:text=${row.getNewName()}></div> + <div th:text=${row.getNewMnemonic()}></div> + <div th:text=${row.getNewMnemonicpath()}></div> + <div th:text=${row.getNewDescription()}></div> + </div> + </td> + + <td th:text="${row.getComment()}"/> + <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/> + <td th:text="${row.getWho()}"/> + </tr> + </tbody> + </table> + <br/> + <hr> + </div> + + <div th:if="${numberCancelled > 0}"> + <div style="padding-bottom: 10px;"> + <h3>Cancelled</h3> + </div> + + <table style="padding-bottom: 10px; width: 90%"> + <thead> + <tr> + <th>Type</th> + <th>Create Update Delete</th> + <th>Approve Cancel Reject</th> + + <th>Old</th> + <th/> + <th>New</th> + + <th>Comment</th> + <th>When</th> + <th>Who</th> + </tr> + </thead> + <tbody> + <tr th:each="row: ${cancelled}"> + <td th:text="${row.getType()}"/> + <td th:text="${row.getStructureCommandCUD()}"/> + <td th:text="${row.getStructureCommandACR()}"/> + + <td style="font-weight: bold;"> + <div> + <div th:text=${row.getOldName()}></div> + <div th:text=${row.getOldMnemonic()}></div> + <div th:text=${row.getOldMnemonicpath()}></div> + <div th:text=${row.getOldDescription()}></div> + </div> + </td> + <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/> + <td style="font-weight: bold;"> + <div> + <div th:text=${row.getNewName()}></div> + <div th:text=${row.getNewMnemonic()}></div> + <div th:text=${row.getNewMnemonicpath()}></div> + <div th:text=${row.getNewDescription()}></div> + </div> + </td> + + <td th:text="${row.getComment()}"/> + <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/> + <td th:text="${row.getWho()}"/> + </tr> + </tbody> + </table> + <br/> + <hr> + </div> + + <div th:if="${numberRejected > 0}"> + <div style="padding-bottom: 10px;"> + <h3>Rejected</h3> + </div> + + <table style="padding-bottom: 10px; width: 90%"> + <thead> + <tr> + <th>Type</th> + <th>Create Update Delete</th> + <th>Approve Cancel Reject</th> + + <th>Old</th> + <th/> + <th>New</th> + + <th>Comment</th> + <th>When</th> + <th>Who</th> + </tr> + </thead> + <tbody> + <tr th:each="row: ${rejected}"> + <td th:text="${row.getType()}"/> + <td th:text="${row.getStructureCommandCUD()}"/> + <td th:text="${row.getStructureCommandACR()}"/> + + <td style="font-weight: bold;"> + <div> + <div th:text=${row.getOldName()}></div> + <div th:text=${row.getOldMnemonic()}></div> + <div th:text=${row.getOldMnemonicpath()}></div> + <div th:text=${row.getOldDescription()}></div> + </div> + </td> + <td style="text-align: center; white-space: nowrap;" th:text="' -> '"/> + <td style="font-weight: bold;"> + <div> + <div th:text=${row.getNewName()}></div> + <div th:text=${row.getNewMnemonic()}></div> + <div th:text=${row.getNewMnemonicpath()}></div> + <div th:text=${row.getNewDescription()}></div> + </div> + </td> + + <td th:text="${row.getComment()}"/> + <td th:text="${#dates.format(row.getWhen(), 'yyyy.MM.dd HH:mm')}"/> + <td th:text="${row.getWho()}"/> + </tr> + </tbody> + </table> + <br/> + <hr> + </div> + + <div th:if="${addfooter}"> + <div th:text="'Notifications show old and new values for Name, Mnemonic, Mnemonic path, Description.'"></div> + <br/> + <hr> + + <div th:text="'Naming URL:'" style="display: inline;"></div> + <a th:href="@{${backendurl}}" th:text="${backendurl}"></a> + </div> + + </body> +</html> \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 0498854c433309a03eac14e1d3c22b413dbb3723..bbcf7817e3c3fdbfa4a7c47caf2f11ce05b499a9 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -12,6 +12,8 @@ logging.level.org.springframework.web=INFO spring.http.log-request-details=true # mail +# administrator mail is comma-separated list of email addresses +naming.mail.administrator=${NAMING_MAIL_ADMINISTRATOR:} 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}