From 4eca26090a8e61f67a15d34a49dc3a31993cfca4 Mon Sep 17 00:00:00 2001 From: Rob McCormack <56261775+robertmccormackbconline@users.noreply.github.com> Date: Thu, 22 Aug 2024 10:53:24 +0100 Subject: [PATCH] DST-16098 Send Domain Event when username is changed (#797) * DST-16098 : Domain Event when username is changed * Fix for failing tests * DST-16098 Feedback * DST-16098 Feedback * Failing test fix --- .../model/entity/DomainEventEntity.java | 44 +++++++++++++ .../model/notification/HmppsDomainEvent.java | 23 +++++++ .../notification/HmppsDomainEventType.java | 14 ++++ .../repository/db/DomainEventRepository.java | 8 +++ .../ndelius/service/DomainEventService.java | 10 +++ .../service/impl/DomainEventServiceImpl.java | 66 +++++++++++++++++++ .../service/impl/UserEntryServiceImpl.java | 18 ++++- src/main/resources/data.sql | 2 + .../controller/UserControllerUpdateTest.java | 7 ++ 9 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 src/main/java/uk/co/bconline/ndelius/model/entity/DomainEventEntity.java create mode 100644 src/main/java/uk/co/bconline/ndelius/model/notification/HmppsDomainEvent.java create mode 100644 src/main/java/uk/co/bconline/ndelius/model/notification/HmppsDomainEventType.java create mode 100644 src/main/java/uk/co/bconline/ndelius/repository/db/DomainEventRepository.java create mode 100644 src/main/java/uk/co/bconline/ndelius/service/DomainEventService.java create mode 100644 src/main/java/uk/co/bconline/ndelius/service/impl/DomainEventServiceImpl.java diff --git a/src/main/java/uk/co/bconline/ndelius/model/entity/DomainEventEntity.java b/src/main/java/uk/co/bconline/ndelius/model/entity/DomainEventEntity.java new file mode 100644 index 00000000..69ed654b --- /dev/null +++ b/src/main/java/uk/co/bconline/ndelius/model/entity/DomainEventEntity.java @@ -0,0 +1,44 @@ +package uk.co.bconline.ndelius.model.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Getter +@Entity +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "DOMAIN_EVENT") +public class DomainEventEntity +{ + @Id + @Column(name = "DOMAIN_EVENT_ID") + @SequenceGenerator(name = "DOMAIN_EVENT_ID_SEQ", sequenceName = "DOMAIN_EVENT_ID_SEQ", allocationSize = 1) + @GeneratedValue(generator = "DOMAIN_EVENT_ID_SEQ") + private Long id; + + @Column(name = "MESSAGE_BODY") + @Lob + private String messageBody; + + @Column(name = "MESSAGE_ATTRIBUTES") + private String messageAttributes; + + @ManyToOne() + @JoinColumn(name = "DOMAIN_EVENT_TYPE_ID", insertable = false, updatable = false) + private ReferenceDataEntity domainEventType; + + @Column(name = "DOMAIN_EVENT_TYPE_ID") + private Long domainEventTypeId; + + @Column(name = "CREATED_DATETIME") + private LocalDateTime createdDateTime; + + @Column(name = "FAILED_PUBLISHING") + private Boolean failedPublishing; +} diff --git a/src/main/java/uk/co/bconline/ndelius/model/notification/HmppsDomainEvent.java b/src/main/java/uk/co/bconline/ndelius/model/notification/HmppsDomainEvent.java new file mode 100644 index 00000000..14dd39ad --- /dev/null +++ b/src/main/java/uk/co/bconline/ndelius/model/notification/HmppsDomainEvent.java @@ -0,0 +1,23 @@ +package uk.co.bconline.ndelius.model.notification; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class HmppsDomainEvent +{ + private String eventType; + private int version; + private String description; + private String occurredAt; + private Map additionalInformation; +} diff --git a/src/main/java/uk/co/bconline/ndelius/model/notification/HmppsDomainEventType.java b/src/main/java/uk/co/bconline/ndelius/model/notification/HmppsDomainEventType.java new file mode 100644 index 00000000..a4a7a828 --- /dev/null +++ b/src/main/java/uk/co/bconline/ndelius/model/notification/HmppsDomainEventType.java @@ -0,0 +1,14 @@ +package uk.co.bconline.ndelius.model.notification; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum HmppsDomainEventType +{ + UMT_USERNAME_CHANGED("probation-user.username.changed", "The username for a probation user has been changed"); + + private final String eventType; + private final String eventDescription; +} diff --git a/src/main/java/uk/co/bconline/ndelius/repository/db/DomainEventRepository.java b/src/main/java/uk/co/bconline/ndelius/repository/db/DomainEventRepository.java new file mode 100644 index 00000000..909d57d7 --- /dev/null +++ b/src/main/java/uk/co/bconline/ndelius/repository/db/DomainEventRepository.java @@ -0,0 +1,8 @@ +package uk.co.bconline.ndelius.repository.db; + +import org.springframework.data.jpa.repository.JpaRepository; +import uk.co.bconline.ndelius.model.entity.DomainEventEntity; + +public interface DomainEventRepository extends JpaRepository +{ +} diff --git a/src/main/java/uk/co/bconline/ndelius/service/DomainEventService.java b/src/main/java/uk/co/bconline/ndelius/service/DomainEventService.java new file mode 100644 index 00000000..c97bbd59 --- /dev/null +++ b/src/main/java/uk/co/bconline/ndelius/service/DomainEventService.java @@ -0,0 +1,10 @@ +package uk.co.bconline.ndelius.service; + +import uk.co.bconline.ndelius.model.notification.HmppsDomainEventType; + +import java.util.Map; + +public interface DomainEventService +{ + void insertDomainEvent(HmppsDomainEventType eventType, Map attributes); +} diff --git a/src/main/java/uk/co/bconline/ndelius/service/impl/DomainEventServiceImpl.java b/src/main/java/uk/co/bconline/ndelius/service/impl/DomainEventServiceImpl.java new file mode 100644 index 00000000..48ad8729 --- /dev/null +++ b/src/main/java/uk/co/bconline/ndelius/service/impl/DomainEventServiceImpl.java @@ -0,0 +1,66 @@ +package uk.co.bconline.ndelius.service.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import uk.co.bconline.ndelius.model.entity.DomainEventEntity; +import uk.co.bconline.ndelius.model.notification.HmppsDomainEvent; +import uk.co.bconline.ndelius.model.notification.HmppsDomainEventType; +import uk.co.bconline.ndelius.repository.db.DomainEventRepository; +import uk.co.bconline.ndelius.repository.db.ReferenceDataRepository; +import uk.co.bconline.ndelius.service.DomainEventService; + +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Map; + +@Slf4j +@Service +public class DomainEventServiceImpl implements DomainEventService +{ + private final ReferenceDataRepository referenceDataRepository; + + private final DomainEventRepository domainEventRepository; + + private final ObjectMapper mapper; + + private static final String DOMAIN_EVENT_TYPE_REF_DATA_CODE_SET = "DOMAIN EVENT TYPE"; + + @Autowired + public DomainEventServiceImpl( + ReferenceDataRepository referenceDataRepository, + DomainEventRepository domainEventRepository, + ObjectMapper mapper) { + this.referenceDataRepository = referenceDataRepository; + this.domainEventRepository = domainEventRepository; + this.mapper = mapper; + } + + @Override + @SneakyThrows + public void insertDomainEvent(HmppsDomainEventType eventType, Map additionalInformation) + { + val type = referenceDataRepository.findByCodeAndReferenceDataMasterCodeSetName(eventType.getEventType(), DOMAIN_EVENT_TYPE_REF_DATA_CODE_SET) + .orElseThrow(() -> new IllegalStateException("Reference data for domain event type " + eventType.getEventType() + " not found")); + val message = HmppsDomainEvent.builder() + .eventType(eventType.getEventType()) + .description(eventType.getEventDescription()) + .occurredAt(ZonedDateTime.now().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)) + .additionalInformation(additionalInformation) + .version(1) + .build(); + val attributes = Map.of("eventType", Map.of("Type", "String", "Value", eventType.getEventType())); + val entity = DomainEventEntity.builder() + .messageBody(mapper.writeValueAsString(message)) + .messageAttributes(mapper.writeValueAsString(attributes)) + .domainEventTypeId(type.getId()) + .createdDateTime(LocalDateTime.now()) + .build(); + + domainEventRepository.save(entity); + } +} diff --git a/src/main/java/uk/co/bconline/ndelius/service/impl/UserEntryServiceImpl.java b/src/main/java/uk/co/bconline/ndelius/service/impl/UserEntryServiceImpl.java index 4311068b..8958d8f9 100644 --- a/src/main/java/uk/co/bconline/ndelius/service/impl/UserEntryServiceImpl.java +++ b/src/main/java/uk/co/bconline/ndelius/service/impl/UserEntryServiceImpl.java @@ -20,8 +20,10 @@ import uk.co.bconline.ndelius.model.entry.UserEntry; import uk.co.bconline.ndelius.model.entry.UserPreferencesEntry; import uk.co.bconline.ndelius.model.entry.projections.UserHomeAreaProjection; +import uk.co.bconline.ndelius.model.notification.HmppsDomainEventType; import uk.co.bconline.ndelius.repository.ldap.UserEntryRepository; import uk.co.bconline.ndelius.repository.ldap.UserPreferencesRepository; +import uk.co.bconline.ndelius.service.DomainEventService; import uk.co.bconline.ndelius.service.GroupService; import uk.co.bconline.ndelius.service.UserEntryService; import uk.co.bconline.ndelius.service.UserRoleService; @@ -29,6 +31,7 @@ import uk.co.bconline.ndelius.util.SearchUtils; import javax.naming.Name; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -68,6 +71,7 @@ public class UserEntryServiceImpl implements UserEntryService, UserDetailsServic private final LdapTemplate ldapTemplate; private final LdapTemplate exportLdapTemplate; private final SearchResultTransformer searchResultTransformer; + private final DomainEventService domainEventService; @Autowired public UserEntryServiceImpl( @@ -77,7 +81,8 @@ public UserEntryServiceImpl( GroupService groupService, LdapTemplate ldapTemplate, @Qualifier("exportLdapTemplate") LdapTemplate exportLdapTemplate, - SearchResultTransformer searchResultTransformer) { + SearchResultTransformer searchResultTransformer, + DomainEventService domainEventService) { this.userRepository = userRepository; this.preferencesRepository = preferencesRepository; this.userRoleService = userRoleService; @@ -85,6 +90,7 @@ public UserEntryServiceImpl( this.ldapTemplate = ldapTemplate; this.exportLdapTemplate = exportLdapTemplate; this.searchResultTransformer = searchResultTransformer; + this.domainEventService = domainEventService; } @Override @@ -253,7 +259,8 @@ public void save(UserEntry user) { } @Override - public void save(String existingUsername, UserEntry user) { + public void save(String existingUsername, UserEntry user) + { // Keep hold of the new username, if it's different we'll rename it later val newUsername = user.getUsername(); user.setUsername(existingUsername); @@ -267,6 +274,13 @@ public void save(String existingUsername, UserEntry user) { val newDn = LdapNameBuilder.newInstance(getDn(newUsername)).build(); log.debug("Renaming LDAP entry from {} to {}", oldDn, newDn); ldapTemplate.rename(oldDn, newDn); + + // Send Domain event + val additionalInformation = Map.of( + "fromUsername", existingUsername, + "toUsername", newUsername + ); + domainEventService.insertDomainEvent(HmppsDomainEventType.UMT_USERNAME_CHANGED, additionalInformation); } } diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index d0346974..da766086 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -44,6 +44,8 @@ INSERT INTO R_REFERENCE_DATA_MASTER (REFERENCE_DATA_MASTER_ID, CODE_SET_NAME) VA INSERT INTO R_STANDARD_REFERENCE_LIST (STANDARD_REFERENCE_LIST_ID, CODE_VALUE, CODE_DESCRIPTION, SELECTABLE, REFERENCE_DATA_MASTER_ID) VALUES (STANDARD_REFERENCE_LIST_ID_SEQ.NEXTVAL, 'GRADE1', 'Grade 1', 'Y', (SELECT REFERENCE_DATA_MASTER_ID FROM R_REFERENCE_DATA_MASTER WHERE CODE_SET_NAME = 'OFFICER GRADE')); INSERT INTO R_STANDARD_REFERENCE_LIST (STANDARD_REFERENCE_LIST_ID, CODE_VALUE, CODE_DESCRIPTION, SELECTABLE, REFERENCE_DATA_MASTER_ID) VALUES (STANDARD_REFERENCE_LIST_ID_SEQ.NEXTVAL, 'GRADE2', 'Grade 2', 'Y', (SELECT REFERENCE_DATA_MASTER_ID FROM R_REFERENCE_DATA_MASTER WHERE CODE_SET_NAME = 'OFFICER GRADE')); INSERT INTO R_STANDARD_REFERENCE_LIST (STANDARD_REFERENCE_LIST_ID, CODE_VALUE, CODE_DESCRIPTION, SELECTABLE, REFERENCE_DATA_MASTER_ID) VALUES (STANDARD_REFERENCE_LIST_ID_SEQ.NEXTVAL, 'GRADE3', 'Grade 3', 'N', (SELECT REFERENCE_DATA_MASTER_ID FROM R_REFERENCE_DATA_MASTER WHERE CODE_SET_NAME = 'OFFICER GRADE')); +INSERT INTO R_REFERENCE_DATA_MASTER (REFERENCE_DATA_MASTER_ID, CODE_SET_NAME) VALUES (REFERENCE_DATA_MASTER_ID_SEQ.NEXTVAL, 'DOMAIN EVENT TYPE'); +INSERT INTO R_STANDARD_REFERENCE_LIST (STANDARD_REFERENCE_LIST_ID, CODE_VALUE, CODE_DESCRIPTION, SELECTABLE, REFERENCE_DATA_MASTER_ID) VALUES (STANDARD_REFERENCE_LIST_ID_SEQ.NEXTVAL, 'probation-user.username.changed', 'probation-user.username.changed', 'Y', (SELECT REFERENCE_DATA_MASTER_ID FROM R_REFERENCE_DATA_MASTER WHERE CODE_SET_NAME = 'DOMAIN EVENT TYPE')); -- Users/Staff INSERT INTO STAFF (STAFF_ID, ROW_VERSION, FORENAME, FORENAME2, SURNAME, OFFICER_CODE, STAFF_GRADE_ID, START_DATE, END_DATE) VALUES (STAFF_ID_SEQ.NEXTVAL, 0, 'dummy', NULL, 'staff', 'N01A000', (SELECT STANDARD_REFERENCE_LIST_ID FROM R_STANDARD_REFERENCE_LIST WHERE CODE_VALUE = 'GRADE1'), CURRENT_TIMESTAMP-10, NULL); diff --git a/src/test/java/uk/co/bconline/ndelius/controller/UserControllerUpdateTest.java b/src/test/java/uk/co/bconline/ndelius/controller/UserControllerUpdateTest.java index 41c2d92b..4165fb4c 100644 --- a/src/test/java/uk/co/bconline/ndelius/controller/UserControllerUpdateTest.java +++ b/src/test/java/uk/co/bconline/ndelius/controller/UserControllerUpdateTest.java @@ -16,6 +16,7 @@ import org.springframework.web.context.WebApplicationContext; import uk.co.bconline.ndelius.model.*; import uk.co.bconline.ndelius.model.entity.StaffEntity; +import uk.co.bconline.ndelius.repository.db.DomainEventRepository; import uk.co.bconline.ndelius.repository.db.StaffRepository; import java.time.LocalDate; @@ -50,6 +51,9 @@ public class UserControllerUpdateTest @Autowired private StaffRepository staffRepository; + @Autowired + private DomainEventRepository domainEventRepository; + private MockMvc mvc; @Before @@ -156,6 +160,7 @@ public void userCanBeRenamed() throws Exception { String username = nextTestUsername(); String token = token(mvc); + int preDomainEventCount = domainEventRepository.findAll().size(); // Given mvc.perform(post("/api/user") @@ -183,6 +188,8 @@ public void userCanBeRenamed() throws Exception .header("Authorization", "Bearer " + token)) .andExpect(status().isOk()) .andExpect(jsonPath("$.username", is(username + "-renamed"))); + + assertEquals(preDomainEventCount + 1, domainEventRepository.findAll().size()); } @Test