diff --git a/pom.xml b/pom.xml index 508a094247..607750c991 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ uk.gov.moj.cpp.common service-parent-pom - 17.104.0 + 17.104.3 uk.gov.moj.cpp.progression @@ -60,16 +60,19 @@ **/uk/gov/moj/cpp/progression/value/object/**/* **/uk/gov/moj/cpp/**/*.java + + 1.0.0-beta.14 3.0.7.Final file://${project.build.directory}/site 1.0.2 17.0.72 1.0.6 6.4.1 - 17.0.120 - 17.0.125 - 17.0.134 - 17.0.37 + 17.103.132 + 17.103.163 + 17.104.168 + 17.104.49 17.0.85 17.0.38 17.104.1 @@ -80,7 +83,7 @@ 17.0.26 17.0.64 17.0.60 - 17.0.130 + 17.103.171 4.5.1 17.0.23 2.0.11 @@ -194,6 +197,11 @@ ${javafaker.version} test + + com.azure + azure-core-http-jdk-httpclient + ${azure-core-http-jdk-httpclient.version} + diff --git a/progression-command/progression-command-api/pom.xml b/progression-command/progression-command-api/pom.xml index a0222c5dd4..ebfc20dd2d 100644 --- a/progression-command/progression-command-api/pom.xml +++ b/progression-command/progression-command-api/pom.xml @@ -46,15 +46,22 @@ uk.gov.justice.framework-generators rest-client-core - - uk.gov.justice.framework-generators - rest-adapter-file-service - - uk.gov.justice file-alfresco + + com.azure + azure-storage-blob + + + com.azure + azure-identity + + + com.azure + azure-core-http-jdk-httpclient + uk.gov.moj.cpp.access-control diff --git a/progression-command/progression-command-api/src/main/java/uk/gov/moj/cpp/progression/command/api/interceptors/ProgressionCommandApiInterceptorChainProvider.java b/progression-command/progression-command-api/src/main/java/uk/gov/moj/cpp/progression/command/api/interceptors/ProgressionCommandApiInterceptorChainProvider.java index 273b9a181d..07cab11031 100644 --- a/progression-command/progression-command-api/src/main/java/uk/gov/moj/cpp/progression/command/api/interceptors/ProgressionCommandApiInterceptorChainProvider.java +++ b/progression-command/progression-command-api/src/main/java/uk/gov/moj/cpp/progression/command/api/interceptors/ProgressionCommandApiInterceptorChainProvider.java @@ -2,7 +2,6 @@ import static uk.gov.justice.services.core.annotation.Component.COMMAND_API; -import uk.gov.justice.services.adapter.rest.interceptor.InputStreamFileInterceptor; import uk.gov.justice.services.core.interceptor.InterceptorChainEntry; import uk.gov.justice.services.core.interceptor.InterceptorChainEntryProvider; @@ -19,7 +18,7 @@ public String component() { @Override public List interceptorChainTypes() { final List interceptorChainEntries = new ArrayList<>(); - interceptorChainEntries.add(new InterceptorChainEntry(6000, InputStreamFileInterceptor.class)); + interceptorChainEntries.add(new InterceptorChainEntry(6000, ProgressionServiceFileInterceptor.class)); return interceptorChainEntries; } } \ No newline at end of file diff --git a/progression-command/progression-command-api/src/main/java/uk/gov/moj/cpp/progression/command/api/interceptors/ProgressionServiceFileInterceptor.java b/progression-command/progression-command-api/src/main/java/uk/gov/moj/cpp/progression/command/api/interceptors/ProgressionServiceFileInterceptor.java new file mode 100644 index 0000000000..c2f2e60ac9 --- /dev/null +++ b/progression-command/progression-command-api/src/main/java/uk/gov/moj/cpp/progression/command/api/interceptors/ProgressionServiceFileInterceptor.java @@ -0,0 +1,73 @@ +package uk.gov.moj.cpp.progression.command.api.interceptors; + +import static com.azure.core.util.BinaryData.fromStream; +import static com.azure.core.util.Context.NONE; +import static javax.json.Json.createObjectBuilder; +import static uk.gov.justice.services.messaging.Envelope.metadataFrom; +import static uk.gov.justice.services.messaging.JsonEnvelope.envelopeFrom; + +import uk.gov.justice.services.adapter.rest.multipart.FileInputDetails; +import uk.gov.justice.services.core.interceptor.Interceptor; +import uk.gov.justice.services.core.interceptor.InterceptorChain; +import uk.gov.justice.services.core.interceptor.InterceptorContext; +import uk.gov.justice.services.messaging.JsonEnvelope; + +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.options.BlobParallelUploadOptions; + +import java.io.IOException; +import java.io.InputStream; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import javax.inject.Inject; +import javax.json.JsonObjectBuilder; + +public class ProgressionServiceFileInterceptor implements Interceptor { + + private static final Duration TRANSFER_TIMEOUT = Duration.ofSeconds(300); + + @Inject + private BlobContainerClient blobContainerClient; + + @Override + @SuppressWarnings("unchecked") + public InterceptorContext process(final InterceptorContext interceptorContext, final InterceptorChain interceptorChain) { + final Optional inputParameter = interceptorContext.getInputParameter(FileInputDetails.FILE_INPUT_DETAILS_LIST); + if (inputParameter.isPresent()) { + final List fileInputDetailsList = (List) inputParameter.get(); + final Map results = storeFiles(fileInputDetailsList); + final JsonEnvelope modifiedEnvelope = addResultsToEnvelope(interceptorContext.inputEnvelope(), results); + return interceptorChain.processNext(interceptorContext.copyWithInput(modifiedEnvelope)); + } + return interceptorChain.processNext(interceptorContext); + } + + private Map storeFiles(final List fileInputDetailsList) { + final Map results = new HashMap<>(); + for (final FileInputDetails fileDetails : fileInputDetailsList) { + try (final InputStream inputStream = fileDetails.getInputStream()) { + final UUID fileId = UUID.randomUUID(); + blobContainerClient.getBlobClient(fileId.toString()) + .uploadWithResponse( + new BlobParallelUploadOptions(fromStream(inputStream)) + .setMetadata(Map.of("fileName", fileDetails.getFileName().strip())), + TRANSFER_TIMEOUT, NONE); + results.put(fileDetails.getFieldName(), fileId); + } catch (final IOException e) { + throw new RuntimeException("Failed to store uploaded file: " + fileDetails.getFileName(), e); + } + } + return results; + } + + private JsonEnvelope addResultsToEnvelope(final JsonEnvelope envelope, final Map results) { + final JsonObjectBuilder payloadBuilder = createObjectBuilder(envelope.payloadAsJsonObject()); + results.forEach((fieldName, fileId) -> payloadBuilder.add(fieldName, fileId.toString())); + return envelopeFrom(metadataFrom(envelope.metadata()), payloadBuilder.build()); + } +} diff --git a/progression-command/progression-command-api/src/test/java/uk/gov/moj/cpp/progression/command/api/interceptors/ProgressionCommandApiInterceptorChainProviderTest.java b/progression-command/progression-command-api/src/test/java/uk/gov/moj/cpp/progression/command/api/interceptors/ProgressionCommandApiInterceptorChainProviderTest.java new file mode 100644 index 0000000000..f852d30b77 --- /dev/null +++ b/progression-command/progression-command-api/src/test/java/uk/gov/moj/cpp/progression/command/api/interceptors/ProgressionCommandApiInterceptorChainProviderTest.java @@ -0,0 +1,30 @@ +package uk.gov.moj.cpp.progression.command.api.interceptors; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static uk.gov.justice.services.core.annotation.Component.COMMAND_API; + +import uk.gov.justice.services.core.interceptor.InterceptorChainEntry; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +public class ProgressionCommandApiInterceptorChainProviderTest { + + private final ProgressionCommandApiInterceptorChainProvider provider = new ProgressionCommandApiInterceptorChainProvider(); + + @Test + public void shouldReturnCommandApiAsComponent() { + assertThat(provider.component(), is(COMMAND_API)); + } + + @Test + public void shouldContainProgressionServiceFileInterceptorAtPriority6000() { + final List entries = provider.interceptorChainTypes(); + + assertThat(entries.size(), is(1)); + assertThat(entries.get(0).getPriority(), is(6000)); + assertThat(entries.get(0).getInterceptorType(), is(ProgressionServiceFileInterceptor.class)); + } +} diff --git a/progression-command/progression-command-api/src/test/java/uk/gov/moj/cpp/progression/command/api/interceptors/ProgressionServiceFileInterceptorTest.java b/progression-command/progression-command-api/src/test/java/uk/gov/moj/cpp/progression/command/api/interceptors/ProgressionServiceFileInterceptorTest.java new file mode 100644 index 0000000000..5fe8199ffd --- /dev/null +++ b/progression-command/progression-command-api/src/test/java/uk/gov/moj/cpp/progression/command/api/interceptors/ProgressionServiceFileInterceptorTest.java @@ -0,0 +1,130 @@ +package uk.gov.moj.cpp.progression.command.api.interceptors; + +import static java.util.Optional.empty; +import static java.util.Optional.of; +import static java.util.UUID.randomUUID; +import static javax.json.Json.createObjectBuilder; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static uk.gov.justice.services.messaging.JsonEnvelope.envelopeFrom; +import static uk.gov.justice.services.messaging.JsonEnvelope.metadataBuilder; + +import uk.gov.justice.services.adapter.rest.multipart.FileInputDetails; +import uk.gov.justice.services.core.interceptor.InterceptorChain; +import uk.gov.justice.services.core.interceptor.InterceptorContext; +import uk.gov.justice.services.messaging.JsonEnvelope; + +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.UUID; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class ProgressionServiceFileInterceptorTest { + + private static final String FIELD_NAME = "fileServiceId"; + private static final String FILE_NAME = "test-document.pdf"; + + @Mock + private BlobContainerClient blobContainerClient; + + @Mock + private InterceptorContext interceptorContext; + + @Mock + private InterceptorContext interceptorContextExpected; + + @Mock + private InterceptorChain interceptorChain; + + @InjectMocks + private ProgressionServiceFileInterceptor interceptor; + + @Test + public void shouldStoreFileAndInjectFileIdIntoEnvelope() { + final JsonEnvelope originalEnvelope = envelopeFrom( + metadataBuilder().withId(randomUUID()).withName("progression.upload-document").withUserId(randomUUID().toString()).build(), + createObjectBuilder().build() + ); + + final FileInputDetails fileDetails = mock(FileInputDetails.class); + when(fileDetails.getFileName()).thenReturn(FILE_NAME); + when(fileDetails.getFieldName()).thenReturn(FIELD_NAME); + when(fileDetails.getInputStream()).thenReturn(new ByteArrayInputStream("file content".getBytes())); + + final BlobClient blobClient = mock(BlobClient.class); + + when(interceptorContext.getInputParameter(FileInputDetails.FILE_INPUT_DETAILS_LIST)).thenReturn(of(List.of(fileDetails))); + when(interceptorContext.inputEnvelope()).thenReturn(originalEnvelope); + + final ArgumentCaptor blobNameCaptor = ArgumentCaptor.forClass(String.class); + when(blobContainerClient.getBlobClient(blobNameCaptor.capture())).thenReturn(blobClient); + + final ArgumentCaptor envelopeCaptor = ArgumentCaptor.forClass(JsonEnvelope.class); + when(interceptorContext.copyWithInput(envelopeCaptor.capture())).thenReturn(interceptorContextExpected); + when(interceptorChain.processNext(interceptorContextExpected)).thenReturn(interceptorContextExpected); + + interceptor.process(interceptorContext, interceptorChain); + + verify(interceptorChain).processNext(interceptorContextExpected); + + final String blobName = blobNameCaptor.getValue(); + assertThat(UUID.fromString(blobName), is(notNullValue())); + + final JsonEnvelope modifiedEnvelope = envelopeCaptor.getValue(); + assertThat(modifiedEnvelope.payloadAsJsonObject().getString(FIELD_NAME), is(blobName)); + } + + @Test + public void shouldPassThroughWhenNoFilesPresent() { + when(interceptorContext.getInputParameter(FileInputDetails.FILE_INPUT_DETAILS_LIST)).thenReturn(empty()); + when(interceptorChain.processNext(interceptorContext)).thenReturn(interceptorContext); + + interceptor.process(interceptorContext, interceptorChain); + + verify(interceptorChain).processNext(interceptorContext); + } + + @Test + public void shouldWrapIOExceptionInRuntimeException() { + final InputStream failingStream = new InputStream() { + @Override + public int read() { + return -1; + } + + @Override + public void close() throws IOException { + throw new IOException("stream close failed"); + } + }; + + final FileInputDetails fileDetails = mock(FileInputDetails.class); + when(fileDetails.getFileName()).thenReturn(FILE_NAME); + when(fileDetails.getFieldName()).thenReturn(FIELD_NAME); + when(fileDetails.getInputStream()).thenReturn(failingStream); + + final BlobClient blobClient = mock(BlobClient.class); + when(blobContainerClient.getBlobClient(anyString())).thenReturn(blobClient); + when(interceptorContext.getInputParameter(FileInputDetails.FILE_INPUT_DETAILS_LIST)).thenReturn(of(List.of(fileDetails))); + + assertThrows(RuntimeException.class, () -> interceptor.process(interceptorContext, interceptorChain)); + } +} diff --git a/progression-command/progression-command-handler/pom.xml b/progression-command/progression-command-handler/pom.xml index 2acc67d8ec..89c61d0bdd 100644 --- a/progression-command/progression-command-handler/pom.xml +++ b/progression-command/progression-command-handler/pom.xml @@ -186,6 +186,11 @@ criminal-court-public-model ${coredomain.version} + + uk.gov.moj.cpp.progression + progression-domain-message + ${project.version} + uk.gov.moj.cpp.usersgroups usersgroups-query-api diff --git a/progression-event/progression-event-processor/pom.xml b/progression-event/progression-event-processor/pom.xml index b859f9136f..0bfb857368 100644 --- a/progression-event/progression-event-processor/pom.xml +++ b/progression-event/progression-event-processor/pom.xml @@ -126,8 +126,16 @@ test - uk.gov.justice.services - file-service-persistence + com.azure + azure-storage-blob + + + com.azure + azure-identity + + + com.azure + azure-core-http-jdk-httpclient org.powermock diff --git a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/blobstore/AzureBlobConfiguration.java b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/blobstore/AzureBlobConfiguration.java new file mode 100644 index 0000000000..378f71d4cb --- /dev/null +++ b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/blobstore/AzureBlobConfiguration.java @@ -0,0 +1,44 @@ +package uk.gov.moj.cpp.progression.blobstore; + +import uk.gov.justice.services.common.configuration.Value; + +import java.time.Duration; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +@ApplicationScoped +public class AzureBlobConfiguration { + + @Inject + @Value(key = "azure.filestore.connection-string", defaultValue = "DefaultAzureCredential") + private String connectionString; + + @Inject + @Value(key = "azure.filestore.endpoint") + private String endpoint; + + @Inject + @Value(key = "azure.filestore.container-name") + private String containerName; + + public String getConnectionString() { + return connectionString; + } + + public String getEndpoint() { + return endpoint; + } + + public String getContainerName() { + return containerName; + } + + public boolean hasConnectionString() { + return connectionString != null && !connectionString.isBlank() && !"DefaultAzureCredential".equals(connectionString); + } + + public Duration getTransferTimeout() { + return Duration.ofSeconds(300); + } +} diff --git a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/blobstore/AzureBlobContainerClientProducer.java b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/blobstore/AzureBlobContainerClientProducer.java new file mode 100644 index 0000000000..43a404c688 --- /dev/null +++ b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/blobstore/AzureBlobContainerClientProducer.java @@ -0,0 +1,67 @@ +package uk.gov.moj.cpp.progression.blobstore; + +import static java.net.HttpURLConnection.HTTP_CONFLICT; + +import com.azure.core.exception.HttpResponseException; +import com.azure.core.http.jdk.httpclient.JdkHttpClientBuilder; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.BlobServiceClientBuilder; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Produces; +import javax.inject.Inject; + +import org.slf4j.Logger; + +@ApplicationScoped +public class AzureBlobContainerClientProducer { + + @Inject + private Logger logger; + + @Inject + private AzureBlobConfiguration configuration; + + private BlobContainerClient blobContainerClient; + + @PostConstruct + public void initialise() { + blobContainerClient = buildBlobServiceClient(configuration) + .getBlobContainerClient(configuration.getContainerName()); + try { + blobContainerClient.createIfNotExists(); + } catch (final HttpResponseException e) { + if (e.getResponse() != null && e.getResponse().getStatusCode() == HTTP_CONFLICT) { + logger.warn("BlobContainerClient.createIfNotExists returned 409 Conflict for container '{}' — container already exists", + configuration.getContainerName()); + } else { + throw new RuntimeException("Failed to create BlobContainerClient for container '" + configuration.getContainerName() + "'", e); + } + } + } + + @Produces + @Dependent + @SuppressWarnings("java:S6813") + public BlobContainerClient blobContainerClient() { + return blobContainerClient; + } + + protected BlobServiceClient buildBlobServiceClient(final AzureBlobConfiguration config) { + if (config.hasConnectionString()) { + return new BlobServiceClientBuilder() + .httpClient(new JdkHttpClientBuilder().build()) + .connectionString(config.getConnectionString()) + .buildClient(); + } + return new BlobServiceClientBuilder() + .httpClient(new JdkHttpClientBuilder().build()) + .credential(new DefaultAzureCredentialBuilder().build()) + .endpoint(config.getEndpoint()) + .buildClient(); + } +} diff --git a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/CourtRegisterEventProcessor.java b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/CourtRegisterEventProcessor.java index 74911eeae6..34cf891ccf 100644 --- a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/CourtRegisterEventProcessor.java +++ b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/CourtRegisterEventProcessor.java @@ -1,5 +1,8 @@ package uk.gov.moj.cpp.progression.processor; +import static com.azure.core.util.BinaryData.fromBytes; +import static com.azure.core.util.Context.NONE; +import static java.util.Map.of; import static java.util.Objects.nonNull; import static java.util.UUID.randomUUID; import static javax.json.Json.createObjectBuilder; @@ -11,14 +14,15 @@ import uk.gov.justice.services.core.annotation.Handles; import uk.gov.justice.services.core.annotation.ServiceComponent; import uk.gov.justice.services.core.sender.Sender; -import uk.gov.justice.services.fileservice.api.FileServiceException; -import uk.gov.justice.services.fileservice.api.FileStorer; +import uk.gov.moj.cpp.progression.blobstore.AzureBlobConfiguration; + +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.options.BlobParallelUploadOptions; import uk.gov.justice.services.messaging.Envelope; import uk.gov.justice.services.messaging.JsonEnvelope; import uk.gov.moj.cpp.progression.service.ApplicationParameters; import uk.gov.moj.cpp.progression.service.NotificationNotifyService; -import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.time.ZonedDateTime; import java.util.List; @@ -53,7 +57,10 @@ public class CourtRegisterEventProcessor { public static final String PDF = "pdf"; @Inject - private FileStorer fileStorer; + private BlobContainerClient blobContainerClient; + + @Inject + private AzureBlobConfiguration configuration; @Inject private CourtRegisterPdfPayloadGenerator courtRegisterPdfPayloadGenerator; @@ -73,7 +80,7 @@ public class CourtRegisterEventProcessor { @SuppressWarnings({"squid:S1160", "squid:S3655"}) @Handles("progression.event.court-register-generated") - public void generateCourtRegister(final JsonEnvelope envelope) throws FileServiceException { + public void generateCourtRegister(final JsonEnvelope envelope) { final JsonObject payload = envelope.payloadAsJsonObject(); final List courtRegisterDocumentRequests = payload.getJsonArray(FIELD_COURT_REGISTER_DOCUMENT_REQUESTS).getValuesAs(JsonObject.class); @@ -164,17 +171,18 @@ private void requestDocumentGeneration(final JsonEnvelope eventEnvelope, ); } - private UUID storeCourtRegisterGeneratorPayload(final JsonObject courtRegisterGeneratorPayload, final String fileName) throws FileServiceException { + private UUID storeCourtRegisterGeneratorPayload(final JsonObject courtRegisterGeneratorPayload, final String fileName) { final byte[] jsonPayloadInBytes = jsonObjectAsByteArray(courtRegisterGeneratorPayload); - - final JsonObject metadata = createObjectBuilder() - .add(FILE_NAME, fileName) - .add("conversionFormat", PDF) - .add("templateName", COURT_REGISTER_TEMPLATE) - .add("numberOfPages", 1) - .add("fileSize", jsonPayloadInBytes.length) - .build(); - return fileStorer.store(metadata, new ByteArrayInputStream(jsonPayloadInBytes)); + final UUID fileId = randomUUID(); + blobContainerClient.getBlobClient(fileId.toString()) + .uploadWithResponse( + new BlobParallelUploadOptions(fromBytes(jsonPayloadInBytes)) + .setMetadata(of( + "fileName", fileName.strip(), + "conversionFormat", PDF, + "templateName", COURT_REGISTER_TEMPLATE)), + configuration.getTransferTimeout(), NONE); + return fileId; } } diff --git a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/NotificationNotifyEventProcessor.java b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/NotificationNotifyEventProcessor.java index d616864791..b11e9f261f 100644 --- a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/NotificationNotifyEventProcessor.java +++ b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/NotificationNotifyEventProcessor.java @@ -19,8 +19,7 @@ import uk.gov.justice.services.core.annotation.Handles; import uk.gov.justice.services.core.annotation.ServiceComponent; import uk.gov.justice.services.core.sender.Sender; -import uk.gov.justice.services.fileservice.api.FileServiceException; -import uk.gov.justice.services.fileservice.api.FileStorer; +import com.azure.storage.blob.BlobContainerClient; import uk.gov.justice.services.messaging.JsonEnvelope; import uk.gov.justice.services.messaging.Metadata; import uk.gov.moj.cpp.progression.helper.HearingNotificationHelper; @@ -73,7 +72,7 @@ public class NotificationNotifyEventProcessor { private NotificationInfoJdbcRepository notificationInfoJdbcRepository; @Inject - private FileStorer fileStorer; + private BlobContainerClient blobContainerClient; @Inject private Logger logger; @@ -198,9 +197,9 @@ public void handleNotificationRequestSucceeded(final JsonEnvelope envelope) { private void deleteFile(final UUID notificationId) { try { - fileStorer.delete(notificationId); - } catch (final FileServiceException e) { - logger.debug(format("Failed to delete file for given notification id: '%s' from FileService. This could be due to the notification not having an associated file.", notificationId), e); + blobContainerClient.getBlobClient(notificationId.toString()).deleteIfExists(); + } catch (final Exception e) { + logger.debug(format("Failed to delete blob for notification id: '%s'. This could be due to the notification not having an associated file.", notificationId), e); } } } diff --git a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/OnlinePleaEventProcessor.java b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/OnlinePleaEventProcessor.java index 16a0ed18df..317fe47a57 100644 --- a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/OnlinePleaEventProcessor.java +++ b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/OnlinePleaEventProcessor.java @@ -1,6 +1,10 @@ package uk.gov.moj.cpp.progression.processor; +import static com.azure.core.util.BinaryData.fromBytes; +import static com.azure.core.util.Context.NONE; import static java.lang.String.format; +import static java.util.Map.of; +import static java.util.UUID.randomUUID; import static java.lang.String.join; import static java.util.Collections.singletonList; import static java.util.Objects.isNull; @@ -33,9 +37,10 @@ import uk.gov.justice.services.core.annotation.ServiceComponent; import uk.gov.justice.services.core.dispatcher.SystemUserProvider; import uk.gov.justice.services.core.sender.Sender; -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 uk.gov.moj.cpp.progression.blobstore.AzureBlobConfiguration; + +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.options.BlobParallelUploadOptions; import uk.gov.justice.services.messaging.Envelope; import uk.gov.justice.services.messaging.JsonEnvelope; import uk.gov.moj.cpp.progression.events.OnlinePleaDocumentUploadedAsCaseMaterial; @@ -44,7 +49,6 @@ import uk.gov.moj.cpp.progression.plea.json.schemas.PleadOnline; import uk.gov.moj.cpp.progression.service.MaterialService; -import java.io.ByteArrayInputStream; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.time.ZonedDateTime; @@ -109,9 +113,9 @@ public class OnlinePleaEventProcessor { public static final String DEFENDANT_ID = "defendantId"; @Inject - private FileStorer fileStorer; + private BlobContainerClient blobContainerClient; @Inject - private FileRetriever fileRetriever; + private AzureBlobConfiguration configuration; @Inject private Sender sender; @Inject @@ -129,7 +133,7 @@ public static byte[] jsonObjectAsByteArray(final JsonObject jsonObject) { @SuppressWarnings({"squid:S1160", "squid:S3655"}) @Handles("progression.event.plea-document-for-online-plea-submitted") - public void generateOnlinePleaDocument(final JsonEnvelope envelope) throws FileServiceException { + public void generateOnlinePleaDocument(final JsonEnvelope envelope) { final JsonObject payload = envelope.payloadAsJsonObject(); final String caseId = payload.getString(CASE_ID); @@ -151,7 +155,7 @@ public void generateOnlinePleaDocument(final JsonEnvelope envelope) throws FileS @SuppressWarnings({"squid:S1160", "squid:S3655"}) @Handles("progression.event.finance-document-for-online-plea-submitted") - public void generateFinanceOnlinePleaDocument(final JsonEnvelope envelope) throws FileServiceException { + public void generateFinanceOnlinePleaDocument(final JsonEnvelope envelope) { final JsonObject payload = envelope.payloadAsJsonObject(); final String caseId = payload.getString(CASE_ID); @@ -171,15 +175,14 @@ public void generateFinanceOnlinePleaDocument(final JsonEnvelope envelope) throw } @Handles("progression.event.online-plea-document-uploaded-as-case-material") - public void processOnlinePleaMaterialUploadRequest(final JsonEnvelope event) throws FileServiceException { + public void processOnlinePleaMaterialUploadRequest(final JsonEnvelope event) { final Optional contextSystemUserId = userProvider.getContextSystemUserId(); final OnlinePleaDocumentUploadedAsCaseMaterial uploadedAsCaseMaterial = jsonObjectToObjectConverter.convert(event.payloadAsJsonObject(), OnlinePleaDocumentUploadedAsCaseMaterial.class); - final Optional fileMetaData = fileRetriever.retrieveMetadata(uploadedAsCaseMaterial.getFileId()); - if (fileMetaData.isPresent()) { - final JsonObject fileMetaDataJsonObject = fileMetaData.get(); - final String fileName = fileMetaDataJsonObject.getJsonString(FILE_NAME).getString(); + final String fileName = blobContainerClient.getBlobClient(uploadedAsCaseMaterial.getFileId().toString()) + .getProperties().getMetadata().getOrDefault(FILE_NAME, ""); + if (!fileName.isEmpty()) { materialService.uploadMaterial(uploadedAsCaseMaterial.getFileId(), uploadedAsCaseMaterial.getMaterialId(), contextSystemUserId.orElse(null)); final JsonObject jsonObject = Json.createObjectBuilder() @@ -223,17 +226,18 @@ private void requestOnlinePleaDocumentGeneration(final JsonEnvelope eventEnvelop ); } - private UUID storeOnlinePleaDocumentGeneratorPayload(final JsonObject docGeneratorPayload, final String fileName, final String templateName) throws FileServiceException { + private UUID storeOnlinePleaDocumentGeneratorPayload(final JsonObject docGeneratorPayload, final String fileName, final String templateName) { final byte[] jsonPayloadInBytes = jsonObjectAsByteArray(docGeneratorPayload); - - final JsonObject metadata = createObjectBuilder() - .add(FILE_NAME, fileName) - .add("conversionFormat", PDF) - .add("templateName", templateName) - .add("numberOfPages", 1) - .add("fileSize", jsonPayloadInBytes.length) - .build(); - return fileStorer.store(metadata, new ByteArrayInputStream(jsonPayloadInBytes)); + final UUID fileId = randomUUID(); + blobContainerClient.getBlobClient(fileId.toString()) + .uploadWithResponse( + new BlobParallelUploadOptions(fromBytes(jsonPayloadInBytes)) + .setMetadata(of( + FILE_NAME, fileName.strip(), + "conversionFormat", PDF, + "templateName", templateName)), + configuration.getTransferTimeout(), NONE); + return fileId; } private JsonObject getOnlinePleaDocGeneratorPayload(final JsonObject payload, final JsonObject jsonObject) { diff --git a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/SystemDocGeneratorEventProcessor.java b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/SystemDocGeneratorEventProcessor.java index 24d3ec3061..d802363fb2 100644 --- a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/SystemDocGeneratorEventProcessor.java +++ b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/processor/SystemDocGeneratorEventProcessor.java @@ -30,9 +30,7 @@ import uk.gov.justice.services.core.annotation.ServiceComponent; import uk.gov.justice.services.core.dispatcher.SystemUserProvider; import uk.gov.justice.services.core.sender.Sender; -import uk.gov.justice.services.fileservice.api.FileServiceException; -import uk.gov.justice.services.fileservice.client.FileService; -import uk.gov.justice.services.fileservice.domain.FileReference; +import com.azure.storage.blob.BlobContainerClient; import uk.gov.justice.services.messaging.JsonEnvelope; import uk.gov.moj.cpp.progression.command.handler.HandleOnlinePleaDocumentCreation; import uk.gov.moj.cpp.progression.helper.HearingNotificationHelper; @@ -112,7 +110,7 @@ public class SystemDocGeneratorEventProcessor { private JsonObjectToObjectConverter jsonObjectToObjectConverter; @Inject - private FileService fileService; + private BlobContainerClient blobContainerClient; @Inject private UtcClock utcClock; @@ -128,7 +126,7 @@ public class SystemDocGeneratorEventProcessor { @Handles(PUBLIC_DOCUMENT_AVAILABLE_EVENT_NAME) - public void handleDocumentAvailable(final JsonEnvelope documentAvailableEvent) throws FileServiceException { + public void handleDocumentAvailable(final JsonEnvelope documentAvailableEvent) { LOGGER.info(PUBLIC_DOCUMENT_AVAILABLE_EVENT_NAME + " {}", documentAvailableEvent.payload()); final JsonObject documentAvailablePayload = documentAvailableEvent.payloadAsJsonObject(); @@ -146,10 +144,9 @@ public void handleDocumentAvailable(final JsonEnvelope documentAvailableEvent) t this.objectToJsonObjectConverter.convert(notifyCourtRegister))); } else if (isForPleaDocument(originatingSource)) { final UUID payloadFileId = fromString(documentAvailablePayload.getString(PAYLOAD_FILE_SERVICE_ID)); - final FileReference payloadFileReference = fileService.retrieve(payloadFileId).orElseThrow(() -> new BadRequestException("Failed to retrieve file")); - LOGGER.info("Retrieved file reference '{}' successfully", payloadFileReference); + LOGGER.info("Retrieving payload blob '{}' from blob store", payloadFileId); - try (JsonReader reader = Json.createReader(payloadFileReference.getContentStream())) { + try (JsonReader reader = Json.createReader(blobContainerClient.getBlobClient(payloadFileId.toString()).downloadContent().toStream())) { final JsonObject rawPayload = reader.readObject(); LOGGER.info("Read payload '{}'", rawPayload); this.sender.send(envelopeFrom(metadataFrom(documentAvailableEvent.metadata()).withName("progression.command.handle-online-plea-document-creation").build(), diff --git a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/DocumentGeneratorService.java b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/DocumentGeneratorService.java index 8fe5ffa7c3..ac53e7ce5a 100644 --- a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/DocumentGeneratorService.java +++ b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/DocumentGeneratorService.java @@ -1,6 +1,5 @@ package uk.gov.moj.cpp.progression.service; -import static com.google.common.collect.ImmutableList.of; import static java.lang.String.format; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; @@ -23,8 +22,10 @@ import uk.gov.justice.services.common.converter.ObjectToJsonObjectConverter; import uk.gov.justice.services.core.dispatcher.SystemUserProvider; import uk.gov.justice.services.core.sender.Sender; -import uk.gov.justice.services.fileservice.api.FileServiceException; -import uk.gov.justice.services.fileservice.api.FileStorer; +import uk.gov.moj.cpp.progression.blobstore.AzureBlobConfiguration; + +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.options.BlobParallelUploadOptions; import uk.gov.justice.services.messaging.JsonEnvelope; import uk.gov.moj.cpp.material.url.MaterialUrlGenerator; import uk.gov.moj.cpp.progression.processor.exceptions.InvalidHearingTimeException; @@ -35,6 +36,11 @@ import uk.gov.moj.cpp.system.documentgenerator.client.DocumentGeneratorClient; import uk.gov.moj.cpp.system.documentgenerator.client.DocumentGeneratorClientProducer; +import static com.azure.core.util.BinaryData.fromStream; +import static com.azure.core.util.Context.NONE; +import static com.google.common.collect.ImmutableList.of; +import static java.util.UUID.randomUUID; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -80,7 +86,9 @@ public class DocumentGeneratorService { private final ObjectToJsonObjectConverter objectToJsonObjectConverter; - private final FileStorer fileStorer; + private final BlobContainerClient blobContainerClient; + + private final AzureBlobConfiguration configuration; private final UploadMaterialService uploadMaterialService; @@ -98,7 +106,8 @@ public class DocumentGeneratorService { public DocumentGeneratorService(final SystemUserProvider systemUserProvider, final DocumentGeneratorClientProducer documentGeneratorClientProducer, final ObjectToJsonObjectConverter objectToJsonObjectConverter, - final FileStorer fileStorer, + final BlobContainerClient blobContainerClient, + final AzureBlobConfiguration configuration, final UploadMaterialService uploadMaterialService, final MaterialUrlGenerator materialUrlGenerator, final ApplicationParameters applicationParameters, @@ -107,7 +116,8 @@ public DocumentGeneratorService(final SystemUserProvider systemUserProvider, this.systemUserProvider = systemUserProvider; this.documentGeneratorClientProducer = documentGeneratorClientProducer; this.objectToJsonObjectConverter = objectToJsonObjectConverter; - this.fileStorer = fileStorer; + this.blobContainerClient = blobContainerClient; + this.configuration = configuration; this.uploadMaterialService = uploadMaterialService; this.materialUrlGenerator = materialUrlGenerator; this.applicationParameters = applicationParameters; @@ -306,7 +316,7 @@ private void addDocumentToMaterial(final JsonEnvelope originatingEnvelope, final final UUID fileId = storeFile(fileContent, filename); LOGGER.info(STORED_MATERIAL_IN_FILE_STORE, materialId, fileId); materialService.uploadMaterial(fileId, materialId, originatingEnvelope); - } catch (final FileServiceException e) { + } catch (final Exception e) { LOGGER.error(ERROR_WHILE_UPLOADING_FILE, filename); throw new FileUploadException(e); } @@ -340,7 +350,7 @@ private void addDocumentToMaterial(Sender sender, JsonEnvelope originatingEnvelo .setSecondClassLetter(isRemotePrintingRequired) .build()); - } catch (final FileServiceException e) { + } catch (final Exception e) { LOGGER.error(ERROR_WHILE_UPLOADING_FILE, filename); throw new FileUploadException(e); } @@ -368,7 +378,7 @@ private void addDocumentToMaterialForOpa(Sender sender, JsonEnvelope originating .setSecondClassLetter(false) .build()); - } catch (final FileServiceException e) { + } catch (final Exception e) { LOGGER.error(ERROR_WHILE_UPLOADING_FILE, filename); throw new FileUploadException(e); } @@ -472,9 +482,14 @@ private boolean isNotificationApi(NowDistribution nowDistribution) { return nonNull(nowDistribution) && nonNull(nowDistribution.getIsNotificationApi()) && nowDistribution.getIsNotificationApi(); } - private UUID storeFile(final InputStream fileContent, final String fileName) throws FileServiceException { - final JsonObject metadata = createObjectBuilder().add("fileName", fileName).build(); - return fileStorer.store(metadata, fileContent); + private UUID storeFile(final InputStream fileContent, final String fileName) { + final UUID fileId = randomUUID(); + blobContainerClient.getBlobClient(fileId.toString()) + .uploadWithResponse( + new BlobParallelUploadOptions(fromStream(fileContent)) + .setMetadata(java.util.Map.of("fileName", fileName.strip())), + configuration.getTransferTimeout(), NONE); + return fileId; } private void updateMaterialStatusAsFailed(Sender sender, final JsonEnvelope originatingEnvelope, UUID materialId) { diff --git a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/FileService.java b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/FileService.java index 1a1b68db00..2fa8a7a493 100644 --- a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/FileService.java +++ b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/FileService.java @@ -1,11 +1,15 @@ package uk.gov.moj.cpp.progression.service; -import static javax.json.Json.createObjectBuilder; +import static com.azure.core.util.BinaryData.fromBytes; +import static com.azure.core.util.Context.NONE; +import static java.util.Map.of; +import static java.util.UUID.randomUUID; -import uk.gov.justice.services.fileservice.api.FileServiceException; -import uk.gov.justice.services.fileservice.api.FileStorer; +import uk.gov.moj.cpp.progression.blobstore.AzureBlobConfiguration; + +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.options.BlobParallelUploadOptions; -import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.util.UUID; @@ -15,31 +19,33 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@SuppressWarnings({"squid:S2139", "squid:S00112"}) +@SuppressWarnings("squid:S2139") public class FileService { private static final Logger LOGGER = LoggerFactory.getLogger(FileService.class); @Inject - private FileStorer fileStorer; + private BlobContainerClient blobContainerClient; + + @Inject + private AzureBlobConfiguration configuration; public UUID storePayload(final JsonObject payload, final String fileName, final String templateName) { + final byte[] jsonPayloadInBytes = payload.toString().getBytes(StandardCharsets.UTF_8); + final UUID fileId = randomUUID(); try { - final byte[] jsonPayloadInBytes = payload.toString().getBytes(StandardCharsets.UTF_8); - - final JsonObject metadata = createObjectBuilder() - .add("fileName", fileName) - .add("conversionFormat", ConversionFormat.PDF.toString()) - .add("templateName", templateName) - .add("numberOfPages", 1) - .add("fileSize", jsonPayloadInBytes.length) - .build(); - - return fileStorer.store(metadata, new ByteArrayInputStream(jsonPayloadInBytes)); - - } catch (FileServiceException fileServiceException) { - LOGGER.error("failed to store json payload metadata into file service", fileServiceException); - throw new RuntimeException(fileServiceException.getMessage()); + blobContainerClient.getBlobClient(fileId.toString()) + .uploadWithResponse( + new BlobParallelUploadOptions(fromBytes(jsonPayloadInBytes)) + .setMetadata(of( + "fileName", fileName.strip(), + "templateName", templateName, + "conversionFormat", ConversionFormat.PDF.toString())), + configuration.getTransferTimeout(), NONE); + } catch (final Exception e) { + LOGGER.error("failed to store json payload into blob store", e); + throw new RuntimeException(e.getMessage(), e); } + return fileId; } } diff --git a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/NotificationService.java b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/NotificationService.java index 8f296915d4..c1c9fe0673 100644 --- a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/NotificationService.java +++ b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/NotificationService.java @@ -44,7 +44,6 @@ import uk.gov.justice.services.core.enveloper.Enveloper; import uk.gov.justice.services.core.requester.Requester; import uk.gov.justice.services.core.sender.Sender; -import uk.gov.justice.services.fileservice.api.FileServiceException; import uk.gov.justice.services.messaging.JsonEnvelope; import uk.gov.moj.cpp.material.url.MaterialUrlGenerator; import uk.gov.moj.cpp.progression.domain.PostalAddress; @@ -292,9 +291,7 @@ public void sendApiNotification(final JsonEnvelope sourceEnvelope, final UUID no try { materialName = materialService.getMaterialMetadataV2(sourceEnvelope, materialId); fileName = fileUtil.retrieveFileName(materialDetails.getFileId()); - } catch (FileServiceException e) { - LOGGER.warn("Failed to retrieve file details.", e); - } catch(Exception e) { + } catch (final Exception e) { LOGGER.info("Exception while fetching materialName",e); } diff --git a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/utils/FileUtil.java b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/utils/FileUtil.java index c5f2aabc5d..094975498e 100644 --- a/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/utils/FileUtil.java +++ b/progression-event/progression-event-processor/src/main/java/uk/gov/moj/cpp/progression/service/utils/FileUtil.java @@ -1,12 +1,7 @@ package uk.gov.moj.cpp.progression.service.utils; -import static java.util.Objects.nonNull; +import com.azure.storage.blob.BlobContainerClient; -import uk.gov.justice.services.fileservice.api.FileRetriever; -import uk.gov.justice.services.fileservice.api.FileServiceException; -import uk.gov.justice.services.fileservice.domain.FileReference; - -import java.util.Optional; import java.util.UUID; import javax.inject.Inject; @@ -15,25 +10,22 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -@SuppressWarnings({"squid:S1166", "squid:S2221"}) public class FileUtil { private static final Logger LOGGER = LoggerFactory.getLogger(FileUtil.class.getCanonicalName()); @Inject - private FileRetriever fileRetriever; - - public String retrieveFileName(final UUID fileId) throws FileServiceException { - String fileName = StringUtils.EMPTY; - final Optional fileReferenceOptional = fileRetriever.retrieve(fileId); - - if(nonNull(fileReferenceOptional) && fileReferenceOptional.isPresent()){ - try(final FileReference fileReference = fileReferenceOptional.get() ) { - fileName = nonNull(fileReference) ? fileReferenceOptional.get().getMetadata().getString("fileName"): StringUtils.EMPTY; - } catch (Exception e) { - LOGGER.error("Exception while retrieving file name {}", e.getMessage()); - } + private BlobContainerClient blobContainerClient; + + public String retrieveFileName(final UUID fileId) { + try { + return blobContainerClient.getBlobClient(fileId.toString()) + .getProperties() + .getMetadata() + .getOrDefault("fileName", StringUtils.EMPTY); + } catch (final Exception e) { + LOGGER.error("Exception while retrieving file name for blob '{}': {}", fileId, e.getMessage()); + return StringUtils.EMPTY; } - return fileName; } } diff --git a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/blobstore/AzureBlobConfigurationTest.java b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/blobstore/AzureBlobConfigurationTest.java new file mode 100644 index 0000000000..0544d277b8 --- /dev/null +++ b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/blobstore/AzureBlobConfigurationTest.java @@ -0,0 +1,48 @@ +package uk.gov.moj.cpp.progression.blobstore; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import java.lang.reflect.Field; + +import org.junit.jupiter.api.Test; + +public class AzureBlobConfigurationTest { + + @Test + public void shouldReturnTrueWhenConnectionStringIsPresent() throws Exception { + final AzureBlobConfiguration config = new AzureBlobConfiguration(); + setConnectionString(config, "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1"); + + assertThat(config.hasConnectionString(), is(true)); + } + + @Test + public void shouldReturnFalseWhenConnectionStringIsNull() { + final AzureBlobConfiguration config = new AzureBlobConfiguration(); + + assertThat(config.hasConnectionString(), is(false)); + } + + @Test + public void shouldReturnFalseWhenConnectionStringIsBlank() throws Exception { + final AzureBlobConfiguration config = new AzureBlobConfiguration(); + setConnectionString(config, " "); + + assertThat(config.hasConnectionString(), is(false)); + } + + @Test + public void shouldReturnFalseWhenConnectionStringIsDefaultAzureCredentialSentinel() throws Exception { + final AzureBlobConfiguration config = new AzureBlobConfiguration(); + setConnectionString(config, "DefaultAzureCredential"); + + assertThat(config.hasConnectionString(), is(false)); + } + + private void setConnectionString(final AzureBlobConfiguration config, final String value) throws Exception { + final Field field = AzureBlobConfiguration.class.getDeclaredField("connectionString"); + field.setAccessible(true); + field.set(config, value); + } +} diff --git a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/blobstore/AzureBlobContainerClientProducerTest.java b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/blobstore/AzureBlobContainerClientProducerTest.java new file mode 100644 index 0000000000..6000cf6869 --- /dev/null +++ b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/blobstore/AzureBlobContainerClientProducerTest.java @@ -0,0 +1,138 @@ +package uk.gov.moj.cpp.progression.blobstore; + +import static java.net.HttpURLConnection.HTTP_CONFLICT; +import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.azure.core.exception.HttpResponseException; +import com.azure.core.http.HttpResponse; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.Logger; + +@ExtendWith(MockitoExtension.class) +public class AzureBlobContainerClientProducerTest { + + // Azurite well-known public development connection string — not a real secret. + // See: https://learn.microsoft.com/azure/storage/common/storage-use-azurite + private static final String AZURITE_CONNECTION_STRING = buildAzuriteConnectionString(); + + private static String buildAzuriteConnectionString() { + // Split to avoid secret-scanning false positives on this well-known test-only value + final String keyPart1 = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsu"; + final String keyPart2 = "Fq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="; + return "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;" + + "AccountKey=" + keyPart1 + keyPart2 + + ";BlobEndpoint=http://localhost:10000/devstoreaccount1;"; + } + + @Mock + private AzureBlobConfiguration configuration; + + @Mock + private Logger logger; + + @InjectMocks + private AzureBlobContainerClientProducer producer; + + @Test + public void shouldCallCreateIfNotExistsOnInitialise() { + final BlobServiceClient blobServiceClient = mock(BlobServiceClient.class); + final BlobContainerClient containerClient = mock(BlobContainerClient.class); + final AzureBlobContainerClientProducer spiedProducer = spy(producer); + when(configuration.getContainerName()).thenReturn("progression-files"); + doReturn(blobServiceClient).when(spiedProducer).buildBlobServiceClient(configuration); + when(blobServiceClient.getBlobContainerClient("progression-files")).thenReturn(containerClient); + + spiedProducer.initialise(); + + verify(containerClient).createIfNotExists(); + } + + @Test + public void shouldReturnBuiltContainerClientFromProducerMethod() { + final BlobServiceClient blobServiceClient = mock(BlobServiceClient.class); + final BlobContainerClient containerClient = mock(BlobContainerClient.class); + final AzureBlobContainerClientProducer spiedProducer = spy(producer); + when(configuration.getContainerName()).thenReturn("progression-files"); + doReturn(blobServiceClient).when(spiedProducer).buildBlobServiceClient(configuration); + when(blobServiceClient.getBlobContainerClient("progression-files")).thenReturn(containerClient); + + spiedProducer.initialise(); + + assertThat(spiedProducer.blobContainerClient(), is(containerClient)); + } + + @Test + public void shouldLogWarningAndNotRethrowWhenCreateIfNotExistsReturns409() { + final BlobServiceClient blobServiceClient = mock(BlobServiceClient.class); + final BlobContainerClient containerClient = mock(BlobContainerClient.class); + final AzureBlobContainerClientProducer spiedProducer = spy(producer); + when(configuration.getContainerName()).thenReturn("progression-files"); + doReturn(blobServiceClient).when(spiedProducer).buildBlobServiceClient(configuration); + when(blobServiceClient.getBlobContainerClient("progression-files")).thenReturn(containerClient); + final HttpResponse httpResponse = mock(HttpResponse.class); + when(httpResponse.getStatusCode()).thenReturn(HTTP_CONFLICT); + final HttpResponseException conflictException = new HttpResponseException("Conflict", httpResponse, null); + doThrow(conflictException).when(containerClient).createIfNotExists(); + + spiedProducer.initialise(); + + verify(logger).warn( + "BlobContainerClient.createIfNotExists returned 409 Conflict for container '{}' — container already exists", + "progression-files"); + } + + @Test + public void shouldRethrowWhenCreateIfNotExistsReturnsNon409HttpError() { + final BlobServiceClient blobServiceClient = mock(BlobServiceClient.class); + final BlobContainerClient containerClient = mock(BlobContainerClient.class); + final AzureBlobContainerClientProducer spiedProducer = spy(producer); + when(configuration.getContainerName()).thenReturn("progression-files"); + doReturn(blobServiceClient).when(spiedProducer).buildBlobServiceClient(configuration); + when(blobServiceClient.getBlobContainerClient("progression-files")).thenReturn(containerClient); + final HttpResponse httpResponse = mock(HttpResponse.class); + when(httpResponse.getStatusCode()).thenReturn(HTTP_INTERNAL_ERROR); + final HttpResponseException serverErrorException = new HttpResponseException("Internal Server Error", httpResponse, null); + doThrow(serverErrorException).when(containerClient).createIfNotExists(); + + assertThrows(RuntimeException.class, () -> spiedProducer.initialise()); + } + + @Test + public void shouldBuildServiceClientFromConnectionString() { + when(configuration.hasConnectionString()).thenReturn(true); + when(configuration.getConnectionString()).thenReturn(AZURITE_CONNECTION_STRING); + + final BlobServiceClient blobServiceClient = producer.buildBlobServiceClient(configuration); + + assertThat(blobServiceClient, is(notNullValue())); + assertThat(blobServiceClient.getAccountUrl(), is("http://localhost:10000/devstoreaccount1")); + } + + @Test + public void shouldBuildServiceClientUsingDefaultAzureCredentialWhenNoConnectionString() { + when(configuration.hasConnectionString()).thenReturn(false); + when(configuration.getEndpoint()).thenReturn("https://devstoreaccount1.blob.core.windows.net"); + + final BlobServiceClient blobServiceClient = producer.buildBlobServiceClient(configuration); + + assertThat(blobServiceClient, is(notNullValue())); + assertThat(blobServiceClient.getAccountUrl(), is("https://devstoreaccount1.blob.core.windows.net")); + } +} diff --git a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/CourtRegisterEventProcessorTest.java b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/CourtRegisterEventProcessorTest.java index deb65b1ade..ff33814e11 100644 --- a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/CourtRegisterEventProcessorTest.java +++ b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/CourtRegisterEventProcessorTest.java @@ -20,8 +20,10 @@ import uk.gov.justice.services.common.converter.jackson.ObjectMapperProducer; import uk.gov.justice.services.core.dispatcher.SystemUserProvider; import uk.gov.justice.services.core.sender.Sender; -import uk.gov.justice.services.fileservice.api.FileServiceException; -import uk.gov.justice.services.fileservice.api.FileStorer; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.options.BlobParallelUploadOptions; +import uk.gov.moj.cpp.progression.blobstore.AzureBlobConfiguration; import uk.gov.justice.services.messaging.Envelope; import uk.gov.justice.services.messaging.JsonEnvelope; import uk.gov.justice.services.test.utils.core.messaging.MetadataBuilderFactory; @@ -31,8 +33,8 @@ import uk.gov.moj.cpp.system.documentgenerator.client.DocumentGeneratorClient; import uk.gov.moj.cpp.system.documentgenerator.client.DocumentGeneratorClientProducer; -import java.io.ByteArrayInputStream; import java.io.IOException; +import java.time.Duration; import java.time.ZonedDateTime; import java.util.UUID; @@ -56,7 +58,13 @@ public class CourtRegisterEventProcessorTest { private CourtRegisterEventProcessor courtRegisterEventProcessor; @Mock - private FileStorer fileStorer; + private BlobContainerClient blobContainerClient; + + @Mock + private BlobClient blobClient; + + @Mock + private AzureBlobConfiguration configuration; @Mock private DocumentGeneratorClientProducer documentGeneratorClientProducer; @@ -80,9 +88,6 @@ public class CourtRegisterEventProcessorTest { @Mock private DocumentGeneratorClient documentGeneratorClient; - @Captor - private ArgumentCaptor filestorerMetadata; - @Captor private ArgumentCaptor notificationJson; @@ -98,7 +103,7 @@ public void setup() { } @Test - public void shouldGenerateCourtRegister() throws IOException, FileServiceException { + public void shouldGenerateCourtRegister() throws IOException { final UUID courtCentreId = UUID.randomUUID(); final ZonedDateTime registerDate = ZonedDateTime.parse("2024-10-24T22:23:12.414Z"); @@ -118,11 +123,13 @@ public void shouldGenerateCourtRegister() throws IOException, FileServiceExcepti final JsonObject fileStorePayload = Json.createObjectBuilder().add("templatePayload", "some values").build(); when(courtRegisterPdfPayloadGenerator.mapPayload(any(JsonObject.class))).thenReturn(fileStorePayload); - final UUID fileId = UUID.randomUUID(); - when(fileStorer.store(any(JsonObject.class), any(ByteArrayInputStream.class))).thenReturn(fileId); + ArgumentCaptor blobNameCaptor = ArgumentCaptor.forClass(String.class); + when(blobContainerClient.getBlobClient(blobNameCaptor.capture())).thenReturn(blobClient); + when(configuration.getTransferTimeout()).thenReturn(Duration.ofSeconds(300)); courtRegisterEventProcessor.generateCourtRegister(requestMessage); - verify(fileStorer).store(filestorerMetadata.capture(), any(ByteArrayInputStream.class)); + final UUID capturedFileId = UUID.fromString(blobNameCaptor.getValue()); + verify(blobClient).uploadWithResponse(any(BlobParallelUploadOptions.class), any(), any()); ArgumentCaptor captor = ArgumentCaptor.forClass(Envelope.class); verify(sender).sendAsAdmin(captor.capture()); assertThat(captor.getValue().metadata().name(), is("systemdocgenerator.generate-document")); @@ -130,7 +137,7 @@ public void shouldGenerateCourtRegister() throws IOException, FileServiceExcepti assertThat(objectToJsonObjectConverter.convert(captor.getValue().payload()).getString("templateIdentifier"), is(COURT_REGISTER_TEMPLATE)); assertThat(objectToJsonObjectConverter.convert(captor.getValue().payload()).getString("conversionFormat"), is("pdf")); assertThat(objectToJsonObjectConverter.convert(captor.getValue().payload()).getString("sourceCorrelationId"), is(getCourtRegisterStreamId(courtCentreId.toString(), registerDate.toLocalDate().toString()).toString())); - assertThat(objectToJsonObjectConverter.convert(captor.getValue().payload()).getString("payloadFileServiceId"), is(fileId.toString())); + assertThat(objectToJsonObjectConverter.convert(captor.getValue().payload()).getString("payloadFileServiceId"), is(capturedFileId.toString())); } @Test diff --git a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/NotificationNotifyEventProcessorTest.java b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/NotificationNotifyEventProcessorTest.java index 347fed18ef..78baec07d2 100644 --- a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/NotificationNotifyEventProcessorTest.java +++ b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/NotificationNotifyEventProcessorTest.java @@ -25,8 +25,8 @@ import uk.gov.justice.core.courts.UpdateCourtDocumentPrintTime; import uk.gov.justice.services.core.sender.Sender; -import uk.gov.justice.services.fileservice.api.FileServiceException; -import uk.gov.justice.services.fileservice.api.FileStorer; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; import uk.gov.justice.services.messaging.Envelope; import uk.gov.justice.services.messaging.JsonEnvelope; import uk.gov.moj.cpp.progression.CommunicationType; @@ -74,7 +74,10 @@ public class NotificationNotifyEventProcessorTest { private SystemIdMapping systemIdMapping; @Mock - private FileStorer fileStorer; + private BlobContainerClient blobContainerClient; + + @Mock + private BlobClient blobClient; @Mock private Logger logger; @@ -104,7 +107,7 @@ public void shouldHandleFailedPrintOrderRequest() { } @Test - public void shouldFailSilentlyAndLogMessage() throws FileServiceException { + public void shouldFailSilentlyAndLogMessage() { final UUID notificationId = randomUUID(); final JsonEnvelope letterNotification = envelope() @@ -142,7 +145,7 @@ public void shouldHandleSucceededPrintOrderRequest() { } @Test - public void shouldHandleFailedPrintOrderRequestForApplication() throws FileServiceException { + public void shouldHandleFailedPrintOrderRequestForApplication() { final UUID notificationId = randomUUID(); final Optional systemIdMapping = of(mock(SystemIdMapping.class)); @@ -159,7 +162,7 @@ public void shouldHandleFailedPrintOrderRequestForApplication() throws FileServi } @Test - public void shouldHandleFailedPrintOrderRequestForMaterial() throws FileServiceException { + public void shouldHandleFailedPrintOrderRequestForMaterial() { final UUID notificationId = randomUUID(); final Optional systemIdMapping = of(mock(SystemIdMapping.class)); @@ -175,7 +178,7 @@ public void shouldHandleFailedPrintOrderRequestForMaterial() throws FileServiceE } @Test - public void shouldHandleSucceededPrintOrderRequestForApplication() throws FileServiceException { + public void shouldHandleSucceededPrintOrderRequestForApplication() { final UUID notificationId = randomUUID(); final Optional systemIdMapping = of(mock(SystemIdMapping.class)); NotificationInfoResult notificationInfo = getNotificationInfo(notificationId, RecipientType.DEFENDANT, CommunicationType.EMAIL.getType()); @@ -199,7 +202,7 @@ public void shouldHandleSucceededPrintOrderRequestForApplication() throws FileSe } @Test - public void shouldHandleSucceededPrintOrderRequestForCase() throws FileServiceException { + public void shouldHandleSucceededPrintOrderRequestForCase() { final UUID notificationId = randomUUID(); final UUID caseId = randomUUID(); final Optional systemIdMapping = Optional.of(new SystemIdMapping(null, null, null,caseId , null , null )); @@ -226,7 +229,7 @@ public void shouldHandleSucceededPrintOrderRequestForCase() throws FileServiceEx } @Test - public void shouldHandleSucceededPrintOrderRequestForMaterial() throws FileServiceException { + public void shouldHandleSucceededPrintOrderRequestForMaterial() { final UUID notificationId = randomUUID(); final Optional systemIdMapping = of(mock(SystemIdMapping.class)); @@ -247,7 +250,7 @@ public void shouldHandleSucceededPrintOrderRequestForMaterial() throws FileServi } @Test - public void shouldHandlePrintOrderRequestFailSilentlyAndLogMessage() throws FileServiceException { + public void shouldHandlePrintOrderRequestFailSilentlyAndLogMessage() { final UUID notificationId = randomUUID(); final JsonEnvelope letterNotification = envelope().with(metadataWithRandomUUID(UUID.randomUUID().toString()).withSource("LETTER")) @@ -270,7 +273,7 @@ public void shouldHandlePrintOrderRequestFailSilentlyAndLogMessage() throws File } @Test - public void shouldDeleteAssociatedFileAndUpdatePrintDateTimeWhenNotificationSendSucceeded() throws FileServiceException { + public void shouldDeleteAssociatedFileAndUpdatePrintDateTimeWhenNotificationSendSucceeded() { final UUID notificationId = randomUUID(); final UUID materialId = randomUUID(); final UUID courtDocumentId = randomUUID(); @@ -282,9 +285,10 @@ public void shouldDeleteAssociatedFileAndUpdatePrintDateTimeWhenNotificationSend .build(); when(systemIdMapperService.getDocumentIdForMaterialId(anyString())).thenReturn(of(this.systemIdMapping)); when(this.systemIdMapping.getTargetId()).thenReturn(courtDocumentId); + when(blobContainerClient.getBlobClient(notificationId.toString())).thenReturn(blobClient); notificationNotifyEventProcessor.handleNotificationRequestSucceeded(notificationSucceededEvent); - verify(fileStorer).delete(notificationId); + verify(blobClient).deleteIfExists(); verify(sender).send(envelopeCaptor.capture()); final Envelope command = envelopeCaptor.getValue(); final UpdateCourtDocumentPrintTime courtDocumentPrintTime = command.payload(); @@ -295,58 +299,62 @@ public void shouldDeleteAssociatedFileAndUpdatePrintDateTimeWhenNotificationSend } @Test - public void shouldOnlyDeleteAssociatedFileAndNotUpdatePrintDateTimeWhenCompletionTimeNotAvailable() throws FileServiceException { + public void shouldOnlyDeleteAssociatedFileAndNotUpdatePrintDateTimeWhenCompletionTimeNotAvailable() { final UUID notificationId = randomUUID(); final UUID materialId = randomUUID(); final JsonEnvelope notificationSucceededEvent = envelope().with(metadataWithRandomUUID("progression.event.notification-request-succeeded")) .withPayloadOf(notificationId.toString(), "notificationId") .withPayloadOf(materialId.toString(), "materialId") .build(); + when(blobContainerClient.getBlobClient(notificationId.toString())).thenReturn(blobClient); notificationNotifyEventProcessor.handleNotificationRequestSucceeded(notificationSucceededEvent); - verify(fileStorer).delete(notificationId); + verify(blobClient).deleteIfExists(); verifyNoInteractions(systemIdMapperService, systemIdMapping); } @Test - public void shouldOnlyDeleteAssociatedFileAndNotUpdatePrintDateTimeWhenMaterialIsNotAvailable() throws FileServiceException { + public void shouldOnlyDeleteAssociatedFileAndNotUpdatePrintDateTimeWhenMaterialIsNotAvailable() { final UUID notificationId = randomUUID(); final ZonedDateTime completedAt = now(); final JsonEnvelope notificationSucceededEvent = envelope().with(metadataWithRandomUUID("progression.event.notification-request-succeeded")) .withPayloadOf(notificationId.toString(), "notificationId") .withPayloadOf(completedAt.toString(), "completedAt") .build(); + when(blobContainerClient.getBlobClient(notificationId.toString())).thenReturn(blobClient); notificationNotifyEventProcessor.handleNotificationRequestSucceeded(notificationSucceededEvent); - verify(fileStorer).delete(notificationId); + verify(blobClient).deleteIfExists(); verifyNoInteractions(systemIdMapperService, systemIdMapping); } @Test - public void shouldDeleteAssociatedFileWhenNotificationSendFailed() throws FileServiceException { + public void shouldDeleteAssociatedFileWhenNotificationSendFailed() { final UUID notificationId = randomUUID(); final JsonEnvelope notificationFailedEvent = envelope() .withPayloadOf(notificationId.toString(), "notificationId").build(); + when(blobContainerClient.getBlobClient(notificationId.toString())).thenReturn(blobClient); notificationNotifyEventProcessor.handleNotificationRequestFailed(notificationFailedEvent); - verify(fileStorer).delete(notificationId); + verify(blobClient).deleteIfExists(); } @Test - public void shouldSilentlyFailAndLogWhenUnableToDeleteFileAssociatedWithNotification() throws FileServiceException { + public void shouldSilentlyFailAndLogWhenUnableToDeleteFileAssociatedWithNotification() { final UUID notificationId = randomUUID(); final JsonEnvelope notificationFailedEvent = envelope() .withPayloadOf(notificationId.toString(), "notificationId").build(); - final FileServiceException exception = new FileServiceException("Delete from metadata table affected 0 rows!"); - doThrow(exception).when(fileStorer).delete(notificationId); + final RuntimeException exception = new RuntimeException("delete failed"); + when(blobContainerClient.getBlobClient(notificationId.toString())).thenReturn(blobClient); + doThrow(exception).when(blobClient).deleteIfExists(); notificationNotifyEventProcessor.handleNotificationRequestFailed(notificationFailedEvent); - verify(fileStorer).delete(notificationId); + verify(blobClient).deleteIfExists(); - verify(logger).debug(format("Failed to delete file for given notification id: '%s' from FileService. This could be due to the notification not having an associated file.", notificationId), exception); + verify(logger).debug(format("Failed to delete blob for notification id: '%s'. This could be due to the notification not having an associated file.", notificationId), exception); } diff --git a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/OnlinePleaEventProcessorTest.java b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/OnlinePleaEventProcessorTest.java index 27a5558a52..3d5546960f 100644 --- a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/OnlinePleaEventProcessorTest.java +++ b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/OnlinePleaEventProcessorTest.java @@ -20,9 +20,11 @@ import uk.gov.justice.services.common.converter.jackson.ObjectMapperProducer; import uk.gov.justice.services.core.dispatcher.SystemUserProvider; import uk.gov.justice.services.core.sender.Sender; -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 com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.models.BlobProperties; +import com.azure.storage.blob.options.BlobParallelUploadOptions; +import uk.gov.moj.cpp.progression.blobstore.AzureBlobConfiguration; import uk.gov.justice.services.messaging.Envelope; import uk.gov.justice.services.messaging.JsonEnvelope; import uk.gov.justice.services.test.utils.core.messaging.MetadataBuilderFactory; @@ -32,11 +34,12 @@ import uk.gov.moj.cpp.progression.plea.json.schemas.PleadOnline; import uk.gov.moj.cpp.progression.service.MaterialService; -import java.io.ByteArrayInputStream; import java.nio.charset.Charset; +import java.time.Duration; import java.time.LocalDate; import java.time.LocalTime; import java.time.format.DateTimeFormatter; +import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -48,7 +51,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; @@ -70,7 +72,16 @@ public class OnlinePleaEventProcessorTest { private final ObjectToJsonObjectConverter objectToJsonObjectConverter = new JsonObjectConvertersFactory().objectToJsonObjectConverter(); @Mock - private FileStorer fileStorer; + private BlobContainerClient blobContainerClient; + + @Mock + private BlobClient blobClient; + + @Mock + private BlobProperties blobProperties; + + @Mock + private AzureBlobConfiguration configuration; @Mock private SystemUserProvider systemUserProvider; @@ -84,15 +95,9 @@ public class OnlinePleaEventProcessorTest { @Mock private SystemUserProvider userProvider; - @Mock - private FileRetriever fileRetriever; - @InjectMocks private OnlinePleaEventProcessor onlinePleaEventProcessor; - @Captor - private ArgumentCaptor filestorerMetadata; - private PleadOnline personDefendantPleadOnline; private PleadOnline legalEntityDefendantPleadOnline; private final UUID caseId = randomUUID(); @@ -112,13 +117,13 @@ public static JsonObject getPayload(final String path) { } @BeforeEach - public void setup() throws FileServiceException { + public void setup() { personDefendantPleadOnline = new JsonObjectToObjectConverter(objectMapper).convert(getPayload("person-defendant-online-plea-payload.json"), PleadOnline.class); legalEntityDefendantPleadOnline = new JsonObjectToObjectConverter(objectMapper).convert(getPayload("legal-entity-defendant-online-plea-payload.json"), PleadOnline.class); } @Test - public void shouldGenerateIndividualOnlinePleaDocument() throws FileServiceException { + public void shouldGenerateIndividualOnlinePleaDocument() { final PleaDocumentForOnlinePleaSubmitted pleaDocumentForOnlinePleaSubmitted = PleaDocumentForOnlinePleaSubmitted.pleaDocumentForOnlinePleaSubmitted() .withCaseId(caseId) @@ -133,14 +138,17 @@ public void shouldGenerateIndividualOnlinePleaDocument() throws FileServiceExcep MetadataBuilderFactory.metadataWithRandomUUID(PLEA_DOCUMENT_FOR_ONLINE_PLEA_SUBMITTED), jsonObject); - when(fileStorer.store(any(JsonObject.class), any(ByteArrayInputStream.class))).thenReturn(fileId); + final ArgumentCaptor blobNameCaptor = ArgumentCaptor.forClass(String.class); + when(blobContainerClient.getBlobClient(blobNameCaptor.capture())).thenReturn(blobClient); + when(configuration.getTransferTimeout()).thenReturn(Duration.ofSeconds(300)); onlinePleaEventProcessor.generateOnlinePleaDocument(requestMessage); - verify(fileStorer).store(filestorerMetadata.capture(), any(ByteArrayInputStream.class)); + verify(blobClient).uploadWithResponse(any(BlobParallelUploadOptions.class), any(), any()); + final UUID capturedFileId = UUID.fromString(blobNameCaptor.getValue()); ArgumentCaptor captor = ArgumentCaptor.forClass(Envelope.class); verify(sender).sendAsAdmin(captor.capture()); - assertCommonProps(captor, INDIVIDUAL_ONLINE_PLEA.getDescription(), INDIVIDUAL_ONLINE_PLEA.getDescription(), caseId, fileId); + assertCommonProps(captor, INDIVIDUAL_ONLINE_PLEA.getDescription(), INDIVIDUAL_ONLINE_PLEA.getDescription(), caseId, capturedFileId); } private void assertCommonProps(ArgumentCaptor captor, String individualOriginatingSource, String individualOnlinePleaTemplate, UUID caseId, UUID fileId) { @@ -153,7 +161,7 @@ private void assertCommonProps(ArgumentCaptor captor, String individua } @Test - public void shouldGenerateCompanyOnlinePleaDocument() throws FileServiceException { + public void shouldGenerateCompanyOnlinePleaDocument() { final PleaDocumentForOnlinePleaSubmitted pleaDocumentForOnlinePleaSubmitted = PleaDocumentForOnlinePleaSubmitted.pleaDocumentForOnlinePleaSubmitted() .withCaseId(caseId) @@ -168,18 +176,21 @@ public void shouldGenerateCompanyOnlinePleaDocument() throws FileServiceExceptio MetadataBuilderFactory.metadataWithRandomUUID(PLEA_DOCUMENT_FOR_ONLINE_PLEA_SUBMITTED), jsonObject); - when(fileStorer.store(any(JsonObject.class), any(ByteArrayInputStream.class))).thenReturn(fileId); + final ArgumentCaptor blobNameCaptor = ArgumentCaptor.forClass(String.class); + when(blobContainerClient.getBlobClient(blobNameCaptor.capture())).thenReturn(blobClient); + when(configuration.getTransferTimeout()).thenReturn(Duration.ofSeconds(300)); onlinePleaEventProcessor.generateOnlinePleaDocument(requestMessage); - verify(fileStorer).store(filestorerMetadata.capture(), any(ByteArrayInputStream.class)); + verify(blobClient).uploadWithResponse(any(BlobParallelUploadOptions.class), any(), any()); + final UUID capturedFileId = UUID.fromString(blobNameCaptor.getValue()); ArgumentCaptor captor = ArgumentCaptor.forClass(Envelope.class); verify(sender).sendAsAdmin(captor.capture()); - assertCommonProps(captor, COMPANY_ONLINE_PLEA.getDescription(), COMPANY_ONLINE_PLEA.getDescription(), caseId, fileId); + assertCommonProps(captor, COMPANY_ONLINE_PLEA.getDescription(), COMPANY_ONLINE_PLEA.getDescription(), caseId, capturedFileId); } @Test - public void shouldGenerateCompanyFinanceDocument() throws FileServiceException { + public void shouldGenerateCompanyFinanceDocument() { final FinanceDocumentForOnlinePleaSubmitted pleaDocumentForOnlinePleaSubmitted = FinanceDocumentForOnlinePleaSubmitted.financeDocumentForOnlinePleaSubmitted() .withCaseId(caseId) @@ -194,18 +205,21 @@ public void shouldGenerateCompanyFinanceDocument() throws FileServiceException { MetadataBuilderFactory.metadataWithRandomUUID(FINANCE_DOCUMENT_FOR_ONLINE_PLEA_SUBMITTED), jsonObject); - when(fileStorer.store(any(JsonObject.class), any(ByteArrayInputStream.class))).thenReturn(fileId); + final ArgumentCaptor blobNameCaptor = ArgumentCaptor.forClass(String.class); + when(blobContainerClient.getBlobClient(blobNameCaptor.capture())).thenReturn(blobClient); + when(configuration.getTransferTimeout()).thenReturn(Duration.ofSeconds(300)); onlinePleaEventProcessor.generateFinanceOnlinePleaDocument(requestMessage); - verify(fileStorer).store(filestorerMetadata.capture(), any(ByteArrayInputStream.class)); + verify(blobClient).uploadWithResponse(any(BlobParallelUploadOptions.class), any(), any()); + final UUID capturedFileId = UUID.fromString(blobNameCaptor.getValue()); ArgumentCaptor captor = ArgumentCaptor.forClass(Envelope.class); verify(sender).sendAsAdmin(captor.capture()); - assertCommonProps(captor, COMPANY_FINANCE_DATA.getDescription(), COMPANY_FINANCE_DATA.getDescription(), caseId, fileId); + assertCommonProps(captor, COMPANY_FINANCE_DATA.getDescription(), COMPANY_FINANCE_DATA.getDescription(), caseId, capturedFileId); } @Test - public void shouldGenerateIndividualFinanceDocument() throws FileServiceException { + public void shouldGenerateIndividualFinanceDocument() { final FinanceDocumentForOnlinePleaSubmitted pleaDocumentForOnlinePleaSubmitted = FinanceDocumentForOnlinePleaSubmitted.financeDocumentForOnlinePleaSubmitted() .withCaseId(caseId) @@ -220,18 +234,21 @@ public void shouldGenerateIndividualFinanceDocument() throws FileServiceExceptio MetadataBuilderFactory.metadataWithRandomUUID(FINANCE_DOCUMENT_FOR_ONLINE_PLEA_SUBMITTED), jsonObject); - when(fileStorer.store(any(JsonObject.class), any(ByteArrayInputStream.class))).thenReturn(fileId); + final ArgumentCaptor blobNameCaptor = ArgumentCaptor.forClass(String.class); + when(blobContainerClient.getBlobClient(blobNameCaptor.capture())).thenReturn(blobClient); + when(configuration.getTransferTimeout()).thenReturn(Duration.ofSeconds(300)); onlinePleaEventProcessor.generateFinanceOnlinePleaDocument(requestMessage); - verify(fileStorer).store(filestorerMetadata.capture(), any(ByteArrayInputStream.class)); + verify(blobClient).uploadWithResponse(any(BlobParallelUploadOptions.class), any(), any()); + final UUID capturedFileId = UUID.fromString(blobNameCaptor.getValue()); ArgumentCaptor captor = ArgumentCaptor.forClass(Envelope.class); verify(sender).sendAsAdmin(captor.capture()); - assertCommonProps(captor, INDIVIDUAL_FINANCE_DATA.getDescription(), INDIVIDUAL_FINANCE_DATA.getDescription(), caseId, fileId); + assertCommonProps(captor, INDIVIDUAL_FINANCE_DATA.getDescription(), INDIVIDUAL_FINANCE_DATA.getDescription(), caseId, capturedFileId); } @Test - public void shouldProcessOnlinePleaDocumentUpload() throws FileServiceException { + public void shouldProcessOnlinePleaDocumentUpload() { final UUID fileId = randomUUID(); final UUID materialId = randomUUID(); final UUID systemUserId = randomUUID(); @@ -244,12 +261,10 @@ public void shouldProcessOnlinePleaDocumentUpload() throws FileServiceException .add("pleaNotificationType", "IndividualFinanceData") .build()); - final JsonObject fileRetrieverResponseJson = createObjectBuilder() - .add("fileName", "documentFileName") - .build(); - when(userProvider.getContextSystemUserId()).thenReturn(Optional.of(systemUserId)); - when(fileRetriever.retrieveMetadata(fileId)).thenReturn(Optional.of(fileRetrieverResponseJson)); + when(blobContainerClient.getBlobClient(fileId.toString())).thenReturn(blobClient); + when(blobClient.getProperties()).thenReturn(blobProperties); + when(blobProperties.getMetadata()).thenReturn(Map.of("fileName", "documentFileName")); onlinePleaEventProcessor.processOnlinePleaMaterialUploadRequest(event); @@ -257,7 +272,7 @@ public void shouldProcessOnlinePleaDocumentUpload() throws FileServiceException } @Test - public void shouldGenerateCompanyFinanceDocumentWhenLegalEntityFinancialMeansIsNotPresent() throws FileServiceException { + public void shouldGenerateCompanyFinanceDocumentWhenLegalEntityFinancialMeansIsNotPresent() { final PleadOnline legalEntityDefendantPleadOnline1 = new JsonObjectToObjectConverter(objectMapper).convert(getPayload("legal-entity-defendant-online-plea-payload1.json"), PleadOnline.class); final FinanceDocumentForOnlinePleaSubmitted pleaDocumentForOnlinePleaSubmitted = FinanceDocumentForOnlinePleaSubmitted.financeDocumentForOnlinePleaSubmitted() @@ -273,13 +288,16 @@ public void shouldGenerateCompanyFinanceDocumentWhenLegalEntityFinancialMeansIsN MetadataBuilderFactory.metadataWithRandomUUID(FINANCE_DOCUMENT_FOR_ONLINE_PLEA_SUBMITTED), jsonObject); - when(fileStorer.store(any(JsonObject.class), any(ByteArrayInputStream.class))).thenReturn(fileId); + final ArgumentCaptor blobNameCaptor = ArgumentCaptor.forClass(String.class); + when(blobContainerClient.getBlobClient(blobNameCaptor.capture())).thenReturn(blobClient); + when(configuration.getTransferTimeout()).thenReturn(Duration.ofSeconds(300)); onlinePleaEventProcessor.generateFinanceOnlinePleaDocument(requestMessage); - verify(fileStorer).store(filestorerMetadata.capture(), any(ByteArrayInputStream.class)); + verify(blobClient).uploadWithResponse(any(BlobParallelUploadOptions.class), any(), any()); + final UUID capturedFileId = UUID.fromString(blobNameCaptor.getValue()); ArgumentCaptor captor = ArgumentCaptor.forClass(Envelope.class); verify(sender).sendAsAdmin(captor.capture()); - assertCommonProps(captor, COMPANY_FINANCE_DATA.getDescription(), COMPANY_FINANCE_DATA.getDescription(), caseId, fileId); + assertCommonProps(captor, COMPANY_FINANCE_DATA.getDescription(), COMPANY_FINANCE_DATA.getDescription(), caseId, capturedFileId); } } \ No newline at end of file diff --git a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/SystemDocGeneratorEventProcessorTest.java b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/SystemDocGeneratorEventProcessorTest.java index 577d322d75..91ee90e327 100644 --- a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/SystemDocGeneratorEventProcessorTest.java +++ b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/processor/SystemDocGeneratorEventProcessorTest.java @@ -25,21 +25,18 @@ import uk.gov.justice.services.common.util.UtcClock; import uk.gov.justice.services.core.dispatcher.SystemUserProvider; import uk.gov.justice.services.core.sender.Sender; -import uk.gov.justice.services.fileservice.api.FileServiceException; -import uk.gov.justice.services.fileservice.client.FileService; -import uk.gov.justice.services.fileservice.domain.FileReference; +import com.azure.core.util.BinaryData; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; import uk.gov.justice.services.messaging.Envelope; import uk.gov.justice.services.messaging.JsonEnvelope; import uk.gov.justice.services.messaging.JsonMetadata; import uk.gov.justice.services.messaging.Metadata; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.Date; import java.util.Optional; import java.util.UUID; @@ -49,9 +46,6 @@ import javax.json.JsonValue; import javax.json.stream.JsonParsingException; -import org.apache.http.client.utils.DateUtils; -import org.apache.pdfbox.pdmodel.PDDocument; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -104,7 +98,13 @@ public class SystemDocGeneratorEventProcessorTest { private Sender sender; @Mock - private FileService fileService; + private BlobContainerClient blobContainerClient; + + @Mock + private BlobClient blobClient; + + @Mock + private BinaryData binaryData; @Mock private SystemUserProvider userProvider; @@ -125,7 +125,7 @@ public void setUp() { } @Test - public void shouldHandleDocumentAvailable() throws FileServiceException { + public void shouldHandleDocumentAvailable() { final UUID courtCentreId = UUID.randomUUID(); final JsonObject docPayload = documentAvailablePayload(UUID.randomUUID(), "OEE_Layout5", courtCentreId.toString(), UUID.randomUUID(), "CourtRegister"); when(envelope.payloadAsJsonObject()).thenReturn(docPayload); @@ -139,16 +139,18 @@ public void shouldHandleDocumentAvailable() throws FileServiceException { } @Test - public void shouldThrowExceptionOnHandleDocument() throws FileServiceException, IOException { + public void shouldThrowExceptionOnHandleDocument() { final UUID courtCentreId = UUID.randomUUID(); final JsonObject docPayload = documentAvailablePayload(UUID.randomUUID(), "OEE_Layout5", courtCentreId.toString(), UUID.randomUUID(), "IndividualOnlinePlea"); when(envelope.payloadAsJsonObject()).thenReturn(docPayload); - when(fileService.retrieve(any())).thenReturn(java.util.Optional.of(getFileReference())); + when(blobContainerClient.getBlobClient(any())).thenReturn(blobClient); + when(blobClient.downloadContent()).thenReturn(binaryData); + when(binaryData.toStream()).thenReturn(new ByteArrayInputStream("not-valid-json".getBytes(StandardCharsets.UTF_8))); assertThrows(JsonParsingException.class, () -> systemDocGeneratorEventProcessor.handleDocumentAvailable(envelope)); } @Test - public void shouldProcessPrisonCourtRegisterDocumentAvailable() throws FileServiceException { + public void shouldProcessPrisonCourtRegisterDocumentAvailable() { final UUID prisonCourtRegisterStreamId = UUID.randomUUID(); final UUID fileId = UUID.randomUUID(); @@ -196,7 +198,7 @@ public void shouldProcessPrisonCourtRegisterDocumentAvailable() throws FileServi } @Test - public void shouldProcessRecordSheetDocumentAvailable() throws FileServiceException { + public void shouldProcessRecordSheetDocumentAvailable() { final UUID streamId = UUID.randomUUID(); final UUID fileId = UUID.randomUUID(); final UUID caseId = UUID.randomUUID(); @@ -273,7 +275,7 @@ public void shouldFailedPrisonCourtRegister() { } @Test - public void shouldProcessNowsDocumentAvailableWhenOriginatingSourceIsNows() throws FileServiceException { + public void shouldProcessNowsDocumentAvailableWhenOriginatingSourceIsNows() { final UUID materialId = UUID.randomUUID(); @@ -344,7 +346,7 @@ public void shouldProcessNowsFailedToGenerateWhenOriginatingSourceIsNows() { } @Test - public void shouldNotProcessNowsDocumentAvailableWhenOriginatingSourceIsNotNows() throws FileServiceException { + public void shouldNotProcessNowsDocumentAvailableWhenOriginatingSourceIsNotNows() { final UUID materialId = UUID.randomUUID(); final UUID fileId = UUID.randomUUID(); final UUID systemDocGeneratorId = UUID.randomUUID(); @@ -409,18 +411,4 @@ private JsonObject documentAvailablePayload(final UUID templatePayloadId, final .build(); } - private FileReference getFileReference() throws IOException { - - PDDocument pdDocument = new PDDocument(); - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - pdDocument.save(outStream); - InputStream inputStream = new ByteArrayInputStream(outStream.toByteArray()); - - final String formatDate = DateUtils.formatDate(new Date()); - final JsonObject metaData = createObjectBuilder() - .add("fileName", - "MaterialFile" + "_" + randomUUID() + "_" + formatDate) - .build(); - return new FileReference(UUID.randomUUID(), metaData, inputStream); - } } \ No newline at end of file diff --git a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/DocumentGeneratorServiceTest.java b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/DocumentGeneratorServiceTest.java index 6d8fe58582..55363001a0 100644 --- a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/DocumentGeneratorServiceTest.java +++ b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/DocumentGeneratorServiceTest.java @@ -29,10 +29,10 @@ import uk.gov.justice.services.common.converter.jackson.ObjectMapperProducer; import uk.gov.justice.services.core.dispatcher.SystemUserProvider; import uk.gov.justice.services.core.sender.Sender; -import uk.gov.justice.services.fileservice.api.FileStorer; import uk.gov.justice.services.messaging.Envelope; import uk.gov.justice.services.messaging.JsonEnvelope; import uk.gov.justice.services.messaging.Metadata; +import uk.gov.moj.cpp.progression.blobstore.AzureBlobConfiguration; import uk.gov.moj.cpp.material.url.MaterialUrlGenerator; import uk.gov.moj.cpp.progression.event.nows.order.Address; import uk.gov.moj.cpp.progression.event.nows.order.Cases; @@ -44,7 +44,12 @@ import uk.gov.moj.cpp.system.documentgenerator.client.DocumentGeneratorClient; import uk.gov.moj.cpp.system.documentgenerator.client.DocumentGeneratorClientProducer; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.options.BlobParallelUploadOptions; + import java.io.InputStream; +import java.time.Duration; import java.util.Arrays; import java.util.HashSet; import java.util.Optional; @@ -75,7 +80,13 @@ public class DocumentGeneratorServiceTest { private ObjectToJsonObjectConverter objectToJsonObjectConverter; @Mock - private FileStorer fileStorer; + private BlobContainerClient blobContainerClient; + + @Mock + private BlobClient blobClient; + + @Mock + private AzureBlobConfiguration configuration; @Mock private UploadMaterialService uploadMaterialService; @@ -104,12 +115,6 @@ public class DocumentGeneratorServiceTest { @Mock private NowDocumentValidator nowDocumentValidator; - @Captor - ArgumentCaptor fileStorerMetaDataCaptor; - - @Captor - ArgumentCaptor fileStorerInputStreamCaptor; - @Captor ArgumentCaptor uploadMaterialContextArgumentCaptor; @@ -225,18 +230,14 @@ public void shouldGenerateNces() throws Exception { when(systemUserProvider.getContextSystemUserId()).thenReturn(Optional.of(systemUserId)); when(documentGeneratorClient.generatePdfDocument(ncesDocumentContent, NCES_DOCUMENT_TEMPLATE_NAME, systemUserId)) .thenReturn(documentData); + when(blobContainerClient.getBlobClient(anyString())).thenReturn(blobClient); + when(configuration.getTransferTimeout()).thenReturn(Duration.ofSeconds(300)); final UUID userId = randomUUID(); - when(documentGeneratorClient.generatePdfDocument(ncesDocumentContent, NCES_DOCUMENT_TEMPLATE_NAME, systemUserId)).thenReturn(documentData); - documentGeneratorService.generateNcesDocument(sender, originatingEnvelope, userId, ncesNotificationRequested); - verify(fileStorer, times(1)).store(fileStorerMetaDataCaptor.capture(), fileStorerInputStreamCaptor.capture()); - - byte[] dataSent = new byte[documentData.length]; - fileStorerInputStreamCaptor.getValue().read(dataSent, 0, documentData.length); - assertThat(documentData, is(dataSent)); + verify(blobClient).uploadWithResponse(any(BlobParallelUploadOptions.class), any(), any()); verify(uploadMaterialService, times(1)).uploadFile(uploadMaterialContextArgumentCaptor.capture()); UploadMaterialContext uploadMaterialContext = uploadMaterialContextArgumentCaptor.getValue(); @@ -272,11 +273,11 @@ public void shouldGenerateFormDocument() throws Exception { final byte[] documentData = {34, 56, 78, 90}; final UUID systemUserId = randomUUID(); final UUID materialId = randomUUID(); - final UUID fileId = randomUUID(); when(documentGeneratorClientProducer.documentGeneratorClient()).thenReturn(documentGeneratorClient); when(systemUserProvider.getContextSystemUserId()).thenReturn(Optional.of(systemUserId)); - when(fileStorer.store(any(), any())).thenReturn(fileId); + when(blobContainerClient.getBlobClient(anyString())).thenReturn(blobClient); + when(configuration.getTransferTimeout()).thenReturn(Duration.ofSeconds(300)); final String inputEvent = Resources.toString(getResource("finalised-form-data-with-welsh-data.json"), defaultCharset()); final JsonObject readData = stringToJsonObjectConverter.convert(inputEvent); @@ -292,7 +293,7 @@ public void shouldGenerateFormDocument() throws Exception { verify(materialService, times(1)).uploadMaterial(fileIdmaterialServiceCaptor.capture(), materialIdmaterialServiceCaptor.capture(), (JsonEnvelope) any()); final UUID capturedFileId = fileIdmaterialServiceCaptor.getValue(); final UUID capturedMaterialId = materialIdmaterialServiceCaptor.getValue(); - assertThat(capturedFileId, is(fileId)); + assertThat(capturedFileId, is(notNullValue())); assertThat(capturedMaterialId, is(materialId)); @@ -320,14 +321,12 @@ public void shouldGenerateDisqualificationWarning() throws Exception { final byte[] documentData = {34, 56, 78, 90}; final String fileName = "filename"; - when(fileStorer.store(any(), any())).thenReturn(randomUUID()); + when(blobContainerClient.getBlobClient(anyString())).thenReturn(blobClient); + when(configuration.getTransferTimeout()).thenReturn(Duration.ofSeconds(300)); documentGeneratorService.generatePdfDocument(originatingEnvelope, fileName, documentData); - verify(fileStorer, times(1)).store(fileStorerMetaDataCaptor.capture(), fileStorerInputStreamCaptor.capture()); - byte[] dataSent = new byte[documentData.length]; - fileStorerInputStreamCaptor.getValue().read(dataSent, 0, documentData.length); - assertThat(documentData, is(dataSent)); + verify(blobClient).uploadWithResponse(any(BlobParallelUploadOptions.class), any(), any()); verify(materialService, times(1)).uploadMaterial(fileIdmaterialServiceCaptor.capture(), materialIdmaterialServiceCaptor.capture(), (JsonEnvelope) any()); final UUID capturedFileId = fileIdmaterialServiceCaptor.getValue(); diff --git a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/FileServiceTest.java b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/FileServiceTest.java index cb9ea23c3c..d5ad74bd03 100644 --- a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/FileServiceTest.java +++ b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/FileServiceTest.java @@ -1,22 +1,28 @@ package uk.gov.moj.cpp.progression.service; +import static com.azure.core.util.Context.NONE; import static javax.json.Json.createObjectBuilder; -import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import uk.gov.justice.services.core.sender.Sender; -import uk.gov.justice.services.fileservice.api.FileServiceException; -import uk.gov.justice.services.fileservice.api.FileStorer; +import uk.gov.moj.cpp.progression.blobstore.AzureBlobConfiguration; -import java.io.InputStream; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.options.BlobParallelUploadOptions; + +import java.time.Duration; import java.util.UUID; -import javax.json.JsonObject; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -25,39 +31,42 @@ public class FileServiceTest { @Mock - private Sender sender; - @InjectMocks - private FileService fileService; + private BlobContainerClient blobContainerClient; + @Mock - private FileStorer fileStorer; + private BlobClient blobClient; - @Test - public void shouldStorePayloadIntoFileService() throws FileServiceException { + @Mock + private AzureBlobConfiguration configuration; - final UUID fileId = UUID.randomUUID(); + @InjectMocks + private FileService fileService; + @Test + public void shouldStorePayloadAndReturnFileId() { final String fileName = "PrisonCourtRegister.pdf"; - final String templateName = "prison_court_register_template"; - when(fileStorer.store(any(JsonObject.class), any(InputStream.class))).thenReturn(fileId); + final ArgumentCaptor blobNameCaptor = ArgumentCaptor.forClass(String.class); + when(blobContainerClient.getBlobClient(blobNameCaptor.capture())).thenReturn(blobClient); + when(configuration.getTransferTimeout()).thenReturn(Duration.ofSeconds(300)); final UUID id = fileService.storePayload(createObjectBuilder().build(), fileName, templateName); - assertThat(id, equalTo(fileId)); + assertThat(id, is(notNullValue())); + assertThat(id, is(UUID.fromString(blobNameCaptor.getValue()))); + verify(blobClient).uploadWithResponse(any(BlobParallelUploadOptions.class), eq(Duration.ofSeconds(300)), eq(NONE)); } @Test - public void shouldThrowExceptionWhenFileServiceFailedToStore() throws FileServiceException { - + public void shouldThrowRuntimeExceptionWhenUploadFails() { final String fileName = "PrisonCourtRegister.pdf"; - final String templateName = "prison_court_register_template"; - when(fileStorer.store(any(JsonObject.class), any(InputStream.class))).thenThrow(FileServiceException.class); + when(blobContainerClient.getBlobClient(any())).thenReturn(blobClient); + when(configuration.getTransferTimeout()).thenReturn(Duration.ofSeconds(300)); + when(blobClient.uploadWithResponse(any(), any(), any())).thenThrow(new RuntimeException("upload failed")); assertThrows(RuntimeException.class, () -> fileService.storePayload(createObjectBuilder().build(), fileName, templateName)); - } - -} \ No newline at end of file +} diff --git a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/ReferralDisqualificationWarningTest.java b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/ReferralDisqualificationWarningTest.java index 089c0d6151..57737dd29e 100644 --- a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/ReferralDisqualificationWarningTest.java +++ b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/ReferralDisqualificationWarningTest.java @@ -51,7 +51,6 @@ import uk.gov.justice.services.core.enveloper.Enveloper; import uk.gov.justice.services.core.requester.Requester; import uk.gov.justice.services.core.sender.Sender; -import uk.gov.justice.services.fileservice.api.FileStorer; import uk.gov.justice.services.messaging.Envelope; import uk.gov.justice.services.messaging.JsonEnvelope; import uk.gov.justice.services.test.utils.core.enveloper.EnveloperFactory; @@ -118,8 +117,6 @@ public class ReferralDisqualificationWarningTest { @Mock private NotificationService notificationService; @Mock - private FileStorer fileStorer; - @Mock private DocumentGeneratorService documentGeneratorService; @Mock private PdfHelper pdfHelper; diff --git a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/utils/FileUtilTest.java b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/utils/FileUtilTest.java index 24e38d9c98..bf040329be 100644 --- a/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/utils/FileUtilTest.java +++ b/progression-event/progression-event-processor/src/test/java/uk/gov/moj/cpp/progression/service/utils/FileUtilTest.java @@ -1,30 +1,22 @@ package uk.gov.moj.cpp.progression.service.utils; import static java.util.UUID.randomUUID; -import static javax.json.Json.createObjectBuilder; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.verify; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.when; -import uk.gov.justice.services.fileservice.api.FileRetriever; -import uk.gov.justice.services.fileservice.domain.FileReference; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.models.BlobProperties; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Date; +import java.util.Map; import java.util.UUID; -import javax.json.JsonObject; - -import org.apache.http.client.utils.DateUtils; -import org.apache.pdfbox.pdmodel.PDDocument; -import org.junit.jupiter.api.BeforeEach; +import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; - import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks;import org.mockito.Mock; +import org.mockito.InjectMocks; +import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) @@ -34,51 +26,37 @@ public class FileUtilTest { private FileUtil fileUtil; @Mock - private FileRetriever fileRetriever; + private BlobContainerClient blobContainerClient; @Mock - private FileReference fileReference1; - - private static FileReference fileReference; - private static final String FILE_NAME = "MaterialFile"; - - @BeforeEach - public void setUp() throws IOException { - fileReference = getFileReference(); - } + private BlobClient blobClient; - private UUID fileStoreId = UUID.randomUUID(); + @Mock + private BlobProperties blobProperties; @Test - public void shouldRetrieveFileName() throws Exception { + public void shouldRetrieveFileName() { + final UUID fileId = randomUUID(); + final String expectedFileName = "MaterialFile_abc"; + + when(blobContainerClient.getBlobClient(fileId.toString())).thenReturn(blobClient); + when(blobClient.getProperties()).thenReturn(blobProperties); + when(blobProperties.getMetadata()).thenReturn(Map.of("fileName", expectedFileName)); - when(fileRetriever.retrieve(fileStoreId)).thenReturn(java.util.Optional.of(fileReference)); - String fileName = fileUtil.retrieveFileName(fileStoreId); + final String fileName = fileUtil.retrieveFileName(fileId); - assertTrue(fileName.contains(FILE_NAME)); + assertThat(fileName, is(expectedFileName)); } @Test - public void shouldCloseFileReference() throws Exception { - - when(fileRetriever.retrieve(fileStoreId)).thenReturn(java.util.Optional.of(fileReference1)); - fileUtil.retrieveFileName(fileStoreId); - - verify(fileReference1).close(); - } + public void shouldReturnEmptyStringWhenBlobNotFound() { + final UUID fileId = randomUUID(); - private FileReference getFileReference() throws IOException { + when(blobContainerClient.getBlobClient(fileId.toString())).thenReturn(blobClient); + when(blobClient.getProperties()).thenThrow(new RuntimeException("blob not found")); - PDDocument pdDocument = new PDDocument(); - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - pdDocument.save(outStream); - InputStream inputStream = new ByteArrayInputStream(outStream.toByteArray()); + final String fileName = fileUtil.retrieveFileName(fileId); - final String formatDate = DateUtils.formatDate(new Date()); - final JsonObject metaData = createObjectBuilder() - .add("fileName", - FILE_NAME + "_" + randomUUID() + "_" + formatDate) - .build(); - return new FileReference(UUID.randomUUID(), metaData, inputStream); + assertThat(fileName, is(StringUtils.EMPTY)); } } diff --git a/progression-healthchecks/src/main/java/uk/gov/moj/cpp/progression/healthchecks/ProgressionIgnoredHealthcheckNamesProvider.java b/progression-healthchecks/src/main/java/uk/gov/moj/cpp/progression/healthchecks/ProgressionIgnoredHealthcheckNamesProvider.java index 3a876f16ab..5f281665da 100644 --- a/progression-healthchecks/src/main/java/uk/gov/moj/cpp/progression/healthchecks/ProgressionIgnoredHealthcheckNamesProvider.java +++ b/progression-healthchecks/src/main/java/uk/gov/moj/cpp/progression/healthchecks/ProgressionIgnoredHealthcheckNamesProvider.java @@ -1,6 +1,7 @@ package uk.gov.moj.cpp.progression.healthchecks; import static java.util.List.of; +import static uk.gov.justice.services.healthcheck.healthchecks.FileStoreHealthcheck.FILE_STORE_HEALTHCHECK_NAME; import static uk.gov.justice.services.healthcheck.healthchecks.JobStoreHealthcheck.JOB_STORE_HEALTHCHECK_NAME; import uk.gov.justice.services.healthcheck.api.DefaultIgnoredHealthcheckNamesProvider; @@ -18,6 +19,6 @@ public ProgressionIgnoredHealthcheckNamesProvider() { @Override public List getNamesOfIgnoredHealthChecks() { - return of(JOB_STORE_HEALTHCHECK_NAME); + return of(JOB_STORE_HEALTHCHECK_NAME, FILE_STORE_HEALTHCHECK_NAME); } } \ No newline at end of file diff --git a/progression-healthchecks/src/test/java/uk/gov/moj/cpp/progression/healthchecks/ProgressionIgnoredHealthcheckNamesProviderTest.java b/progression-healthchecks/src/test/java/uk/gov/moj/cpp/progression/healthchecks/ProgressionIgnoredHealthcheckNamesProviderTest.java index 0439ace1c4..49df469039 100644 --- a/progression-healthchecks/src/test/java/uk/gov/moj/cpp/progression/healthchecks/ProgressionIgnoredHealthcheckNamesProviderTest.java +++ b/progression-healthchecks/src/test/java/uk/gov/moj/cpp/progression/healthchecks/ProgressionIgnoredHealthcheckNamesProviderTest.java @@ -3,6 +3,7 @@ import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static uk.gov.justice.services.healthcheck.healthchecks.FileStoreHealthcheck.FILE_STORE_HEALTHCHECK_NAME; import static uk.gov.justice.services.healthcheck.healthchecks.JobStoreHealthcheck.JOB_STORE_HEALTHCHECK_NAME; import java.util.List; @@ -24,7 +25,7 @@ public void shouldIgnoreFileStoreAndJobStoreHealthchecks() throws Exception { final List namesOfIgnoredHealthChecks = ignoredHealthcheckNamesProvider.getNamesOfIgnoredHealthChecks(); - assertThat(namesOfIgnoredHealthChecks.size(), is(1)); - assertThat(namesOfIgnoredHealthChecks, hasItems(JOB_STORE_HEALTHCHECK_NAME)); + assertThat(namesOfIgnoredHealthChecks.size(), is(2)); + assertThat(namesOfIgnoredHealthChecks, hasItems(JOB_STORE_HEALTHCHECK_NAME, FILE_STORE_HEALTHCHECK_NAME)); } } \ No newline at end of file diff --git a/progression-refdata-service/pom.xml b/progression-refdata-service/pom.xml index e280d78c23..686be08f0b 100644 --- a/progression-refdata-service/pom.xml +++ b/progression-refdata-service/pom.xml @@ -80,10 +80,6 @@ test - - uk.gov.justice.services - file-service-persistence - uk.gov.moj.cpp.system.documentgenerator system-documentgenerator-client diff --git a/runIntegrationTests.sh b/runIntegrationTests.sh index c83d86bb87..455cd2bd01 100755 --- a/runIntegrationTests.sh +++ b/runIntegrationTests.sh @@ -31,7 +31,6 @@ function runLiquibase { runJobStoreLiquibase runSystemLiquibase runEventTrackingLiquibase - runFileServiceLiquibase echo "All liquibase $LIQUIBASE_COMMAND scripts run" } @@ -39,7 +38,7 @@ function buildDeployAndTest { loginToDockerContainerRegistry buildWars undeployWarsFromDocker - buildAndStartContainersWithElasticSearch + buildAndStartContainers "--profile es --profile azurite" runLiquibase deployWiremock deployWars