Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# AMP-504: PCR rawPayload Forwarding — Implementation Plan (Design 2)
# AMP-504: PCR rawPayload Forwarding — Implementation Plan (Design)

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import uk.gov.justice.core.courts.prisonCourtRegisterDocument.PrisonCourtRegisterDefendant;
import uk.gov.justice.core.courts.prisonCourtRegisterDocument.PrisonCourtRegisterHearingVenue;
import uk.gov.justice.services.common.converter.JsonObjectToObjectConverter;
import uk.gov.justice.services.common.converter.ObjectToJsonObjectConverter;
import uk.gov.justice.services.common.util.UtcClock;
import uk.gov.justice.services.core.annotation.Handles;
import uk.gov.justice.services.core.annotation.ServiceComponent;
Expand Down Expand Up @@ -111,6 +112,9 @@ public class PrisonCourtRegisterEventProcessor {
@Inject
private HearingResultsDocumentSubscriptionClient hearingResultsDocumentSubscriptionClient;

@Inject
private ObjectToJsonObjectConverter objectToJsonObjectConverter;

@SuppressWarnings("squid:S1160")
@Handles("progression.event.prison-court-register-recorded")
public void generatePrisonCourtRegister(final JsonEnvelope envelope) {
Expand Down Expand Up @@ -231,9 +235,12 @@ public void sendPrisonCourtRegisterV2(final JsonEnvelope envelope) throws Inter
? emailRecipients.get(0).getEmail()
: "";
Instant createdAt = envelope.metadata().createdAt().orElse(ZonedDateTime.now()).toInstant();
PcrEventPayload pcrEventPayload = hearingResultsDocumentSubscriptionPCRMapper.mapPcrForhearingResultsDocument(prisonCourtRegisterGenerated, emailRecipient, createdAt);

final UUID fileId = prisonCourtRegisterGenerated.getFileId();
final String rawPayload = fileService.retrievePayload(fileId)
.map(JsonObject::toString)
.orElseGet(() -> objectToJsonObjectConverter.convert(prisonCourtRegisterGenerated).toString());
PcrEventPayload pcrEventPayload = hearingResultsDocumentSubscriptionPCRMapper
.mapPcrForhearingResultsDocument(prisonCourtRegisterGenerated, emailRecipient, createdAt, rawPayload);
final String prisonCourtRegisterId = envelope.payloadAsJsonObject().containsKey("id")
? envelope.payloadAsJsonObject().getString("id")
: fileId.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@

import static javax.json.Json.createObjectBuilder;

import uk.gov.justice.services.fileservice.api.FileRetriever;
import uk.gov.justice.services.fileservice.api.FileServiceException;
import uk.gov.justice.services.fileservice.api.FileStorer;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.UUID;

import javax.inject.Inject;
import javax.json.Json;
import javax.json.JsonObject;

import org.slf4j.Logger;
Expand All @@ -23,6 +28,9 @@ public class FileService {
@Inject
private FileStorer fileStorer;

@Inject
private FileRetriever fileRetriever;

public UUID storePayload(final JsonObject payload, final String fileName, final String templateName) {
try {
final byte[] jsonPayloadInBytes = payload.toString().getBytes(StandardCharsets.UTF_8);
Expand All @@ -42,4 +50,21 @@ public UUID storePayload(final JsonObject payload, final String fileName, final
throw new RuntimeException(fileServiceException.getMessage());
}
}

public Optional<JsonObject> retrievePayload(final UUID fileId) {
try {
return fileRetriever.retrieve(fileId).map(ref -> {
try (InputStream stream = ref.getContentStream()) {
final String json = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
return Json.createReader(new StringReader(json)).readObject();
} catch (java.io.IOException e) {
LOGGER.error("Failed to read content stream for fileId {}", fileId, e);
throw new RuntimeException(e);
}
});
} catch (FileServiceException e) {
LOGGER.error("Failed to retrieve payload from file service for fileId {}", fileId, e);
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package uk.gov.moj.cpp.progression.service.amp.dto;

import com.fasterxml.jackson.annotation.JsonRawValue;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -24,5 +25,7 @@ public class PcrEventPayload {
private UUID materialId;
private Instant timestamp;
private PcrEventPayloadDefendant defendant;
@JsonRawValue
private String rawPayload;
}

Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@
@Slf4j
public class HearingResultsDocumentSubscriptionPCRMapper {

public PcrEventPayload mapPcrForhearingResultsDocument(PrisonCourtRegisterGeneratedV2 pcrIn, String prisonEmail, Instant createdAt) {
public PcrEventPayload mapPcrForhearingResultsDocument(
final PrisonCourtRegisterGeneratedV2 pcrIn,
final String prisonEmail,
final Instant createdAt,
final String rawPayload) {
return PcrEventPayload.builder()
.eventType(PcrEventType.PRISON_COURT_REGISTER_GENERATED)
.eventId(pcrIn.getId())
.hearingId(pcrIn.getHearingId())
.materialId(pcrIn.getMaterialId())
.timestamp(Instant.now())
.timestamp(createdAt)
.defendant(mapDefendant(pcrIn, prisonEmail))
.rawPayload(rawPayload)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import uk.gov.justice.services.common.converter.jackson.ObjectMapperProducer;
import uk.gov.justice.core.courts.PrisonCourtRegisterGenerated;
import uk.gov.justice.core.courts.PrisonCourtRegisterGeneratedV2;
import uk.gov.justice.core.courts.PrisonCourtRegisterRecorded;
Expand Down Expand Up @@ -49,6 +50,8 @@
import static java.util.Collections.singletonList;
import static java.util.UUID.randomUUID;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
Expand All @@ -69,7 +72,8 @@ public class PrisonCourtRegisterEventProcessorTest {
private PrisonCourtRegisterEventProcessor prisonCourtRegisterEventProcessor;
private final ObjectMapper objectMapper = new ObjectMapperProducer().objectMapper();

private final ObjectToJsonObjectConverter objectToJsonObjectConverter = new ObjectToJsonObjectConverter(objectMapper);
@Spy
private ObjectToJsonObjectConverter objectToJsonObjectConverter = new ObjectToJsonObjectConverter(new ObjectMapperProducer().objectMapper());

@Mock
private SystemDocGeneratorService systemDocGeneratorService;
Expand Down Expand Up @@ -227,7 +231,8 @@ public void shouldSendPrisonCourtRegisterV2() throws InterruptedException {
.materialId(randomUUID())
.eventId(randomUUID())
.build();
when(hearingResultsDocumentSubscriptionPCRMapper.mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq("test@hmcst.net"), any(Instant.class))).thenReturn(pcrEventPayload);
when(fileService.retrievePayload(any(UUID.class))).thenReturn(Optional.empty());
when(hearingResultsDocumentSubscriptionPCRMapper.mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq("test@hmcst.net"), any(Instant.class), any())).thenReturn(pcrEventPayload);
when(applicationParameters.getHearingResultsDocumentSubscriptionUrl()).thenReturn("http://amp-url");
when(hearingResultsDocumentSubscriptionClient.post("http://amp-url", pcrEventPayload)).thenReturn(Response.ok().build());
when(applicationParameters.getHearingResultsDocumentSubscriptionRetryTimes()).thenReturn("3");
Expand All @@ -236,7 +241,7 @@ public void shouldSendPrisonCourtRegisterV2() throws InterruptedException {
prisonCourtRegisterEventProcessor.sendPrisonCourtRegisterV2(requestMessage);

verify(hearingResultsDocumentSubscriptionClient).post("http://amp-url", pcrEventPayload);
verify(hearingResultsDocumentSubscriptionPCRMapper).mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq("test@hmcst.net"), any(Instant.class));
verify(hearingResultsDocumentSubscriptionPCRMapper).mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq("test@hmcst.net"), any(Instant.class), any());
}

@Test
Expand Down Expand Up @@ -265,7 +270,8 @@ public void shouldSendPrisonCourtRegisterV2WhenRecipientsHaveNoEmailAddress() th
.build();
// Recipients exist but don't have emailAddress1, so filterEmailRecipients returns empty list
// resulting in empty string for emailRecipient
when(hearingResultsDocumentSubscriptionPCRMapper.mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq(""), any(Instant.class))).thenReturn(pcrEventPayload);
when(fileService.retrievePayload(any(UUID.class))).thenReturn(Optional.empty());
when(hearingResultsDocumentSubscriptionPCRMapper.mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq(""), any(Instant.class), any())).thenReturn(pcrEventPayload);
when(applicationParameters.getHearingResultsDocumentSubscriptionUrl()).thenReturn("http://hrds-address");
when(hearingResultsDocumentSubscriptionClient.post("http://hrds-address", pcrEventPayload)).thenReturn(Response.ok().build());
when(applicationParameters.getHearingResultsDocumentSubscriptionRetryTimes()).thenReturn("3");
Expand All @@ -274,7 +280,7 @@ public void shouldSendPrisonCourtRegisterV2WhenRecipientsHaveNoEmailAddress() th
prisonCourtRegisterEventProcessor.sendPrisonCourtRegisterV2(requestMessage);

verify(hearingResultsDocumentSubscriptionClient).post("http://hrds-address", pcrEventPayload);
verify(hearingResultsDocumentSubscriptionPCRMapper).mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq(""), any(Instant.class));
verify(hearingResultsDocumentSubscriptionPCRMapper).mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq(""), any(Instant.class), any());
}

@Test
Expand All @@ -301,7 +307,8 @@ public void shouldSendPrisonCourtRegisterV2WithMissingIdUsesFileId() throws Inte
.materialId(randomUUID())
.eventId(randomUUID())
.build();
when(hearingResultsDocumentSubscriptionPCRMapper.mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq("test@hmcst.net"), any(Instant.class))).thenReturn(pcrEventPayload);
when(fileService.retrievePayload(any(UUID.class))).thenReturn(Optional.empty());
when(hearingResultsDocumentSubscriptionPCRMapper.mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq("test@hmcst.net"), any(Instant.class), any())).thenReturn(pcrEventPayload);
when(applicationParameters.getHearingResultsDocumentSubscriptionUrl()).thenReturn("http://hrds-address");
when(hearingResultsDocumentSubscriptionClient.post("http://hrds-address", pcrEventPayload)).thenReturn(Response.ok().build());
when(applicationParameters.getHearingResultsDocumentSubscriptionRetryTimes()).thenReturn("3");
Expand All @@ -310,7 +317,7 @@ public void shouldSendPrisonCourtRegisterV2WithMissingIdUsesFileId() throws Inte
prisonCourtRegisterEventProcessor.sendPrisonCourtRegisterV2(requestMessage);

verify(hearingResultsDocumentSubscriptionClient).post("http://hrds-address", pcrEventPayload);
verify(hearingResultsDocumentSubscriptionPCRMapper).mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq("test@hmcst.net"), any(Instant.class));
verify(hearingResultsDocumentSubscriptionPCRMapper).mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq("test@hmcst.net"), any(Instant.class), any());
}

@Test
Expand All @@ -337,7 +344,8 @@ public void shouldSendPrisonCourtRegisterV2WithMissingCreatedAtUsesCurrentTime()
.materialId(randomUUID())
.eventId(randomUUID())
.build();
when(hearingResultsDocumentSubscriptionPCRMapper.mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq("test@hmcst.net"), any(Instant.class))).thenReturn(pcrEventPayload);
when(fileService.retrievePayload(any(UUID.class))).thenReturn(Optional.empty());
when(hearingResultsDocumentSubscriptionPCRMapper.mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq("test@hmcst.net"), any(Instant.class), any())).thenReturn(pcrEventPayload);
when(applicationParameters.getHearingResultsDocumentSubscriptionUrl()).thenReturn("http://hrds-address");
when(hearingResultsDocumentSubscriptionClient.post("http://hrds-address", pcrEventPayload)).thenReturn(Response.ok().build());
when(applicationParameters.getHearingResultsDocumentSubscriptionRetryTimes()).thenReturn("3");
Expand All @@ -346,7 +354,7 @@ public void shouldSendPrisonCourtRegisterV2WithMissingCreatedAtUsesCurrentTime()
prisonCourtRegisterEventProcessor.sendPrisonCourtRegisterV2(requestMessage);

verify(hearingResultsDocumentSubscriptionClient).post("http://hrds-address", pcrEventPayload);
verify(hearingResultsDocumentSubscriptionPCRMapper).mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq("test@hmcst.net"), any(Instant.class));
verify(hearingResultsDocumentSubscriptionPCRMapper).mapPcrForhearingResultsDocument(any(PrisonCourtRegisterGeneratedV2.class), eq("test@hmcst.net"), any(Instant.class), any());
}

@Test
Expand Down Expand Up @@ -376,4 +384,84 @@ public void shouldSendPrisonCourtRegisterWithDefendantHasNoDateOfBirth() {
assertThat(notificationJsonObjectCaptor.getValue().getString("notificationId"), is(notNullValue()));
}

@Test
public void shouldPassRawPayloadFromFileServiceToMapper() throws InterruptedException {
final UUID fileId = randomUUID();
final JsonObject sourcePayloadJson = Json.createObjectBuilder()
.add("courtHouse", "Southwark Crown Court").build();

final PrisonCourtRegisterGeneratedV2 prisonCourtRegisterGeneratedV2 =
PrisonCourtRegisterGeneratedV2.prisonCourtRegisterGeneratedV2()
.withId(randomUUID())
.withFileId(fileId)
.withMaterialId(randomUUID())
.withHearingId(randomUUID())
.withCourtCentreId(randomUUID())
.withRecipients(singletonList(new PrisonCourtRegisterRecipient.Builder()
.withEmailAddress1("prison@example.com")
.withEmailTemplateName("templateName").build()))
.withDefendant(PrisonCourtRegisterDefendant.prisonCourtRegisterDefendant()
.withName("Test Defendant").build())
.build();

final JsonEnvelope requestMessage = envelopeFrom(
MetadataBuilderFactory.metadataWithRandomUUID("progression.event.prison-court-register-generated-v2"),
objectToJsonObjectConverter.convert(prisonCourtRegisterGeneratedV2));

when(applicationParameters.getHearingResultsDocumentSubscriptionUrl()).thenReturn("http://hrds/notifications");
when(applicationParameters.getHearingResultsDocumentSubscriptionRetryTimes()).thenReturn("3");
when(applicationParameters.getHearingResultsDocumentSubscriptionRetryInterval()).thenReturn("1000");
when(fileService.retrievePayload(fileId)).thenReturn(Optional.of(sourcePayloadJson));
when(hearingResultsDocumentSubscriptionClient.post(anyString(), any()))
.thenReturn(Response.ok().build());
when(hearingResultsDocumentSubscriptionPCRMapper.mapPcrForhearingResultsDocument(
any(), anyString(), any(), any())).thenReturn(PcrEventPayload.builder().build());

prisonCourtRegisterEventProcessor.sendPrisonCourtRegisterV2(requestMessage);

final ArgumentCaptor<String> rawPayloadCaptor = ArgumentCaptor.forClass(String.class);
verify(hearingResultsDocumentSubscriptionPCRMapper).mapPcrForhearingResultsDocument(
any(), anyString(), any(), rawPayloadCaptor.capture());
assertThat(rawPayloadCaptor.getValue(), containsString("Southwark Crown Court"));
}

@Test
public void shouldFallBackToV2EventJsonWhenFileServiceReturnsEmpty() throws InterruptedException {
final UUID fileId = randomUUID();

final PrisonCourtRegisterGeneratedV2 prisonCourtRegisterGeneratedV2 =
PrisonCourtRegisterGeneratedV2.prisonCourtRegisterGeneratedV2()
.withId(randomUUID())
.withFileId(fileId)
.withMaterialId(randomUUID())
.withHearingId(randomUUID())
.withCourtCentreId(randomUUID())
.withRecipients(singletonList(new PrisonCourtRegisterRecipient.Builder()
.withEmailAddress1("prison@example.com")
.withEmailTemplateName("templateName").build()))
.withDefendant(PrisonCourtRegisterDefendant.prisonCourtRegisterDefendant()
.withName("Test Defendant").build())
.build();

final JsonEnvelope requestMessage = envelopeFrom(
MetadataBuilderFactory.metadataWithRandomUUID("progression.event.prison-court-register-generated-v2"),
objectToJsonObjectConverter.convert(prisonCourtRegisterGeneratedV2));

when(applicationParameters.getHearingResultsDocumentSubscriptionUrl()).thenReturn("http://hrds/notifications");
when(applicationParameters.getHearingResultsDocumentSubscriptionRetryTimes()).thenReturn("3");
when(applicationParameters.getHearingResultsDocumentSubscriptionRetryInterval()).thenReturn("1000");
when(fileService.retrievePayload(fileId)).thenReturn(Optional.empty());
when(hearingResultsDocumentSubscriptionClient.post(anyString(), any()))
.thenReturn(Response.ok().build());
when(hearingResultsDocumentSubscriptionPCRMapper.mapPcrForhearingResultsDocument(
any(), anyString(), any(), any())).thenReturn(PcrEventPayload.builder().build());

prisonCourtRegisterEventProcessor.sendPrisonCourtRegisterV2(requestMessage);

final ArgumentCaptor<String> rawPayloadCaptor = ArgumentCaptor.forClass(String.class);
verify(hearingResultsDocumentSubscriptionPCRMapper).mapPcrForhearingResultsDocument(
any(), anyString(), any(), rawPayloadCaptor.capture());
assertThat(rawPayloadCaptor.getValue(), is(notNullValue()));
}

}
Loading
Loading