diff --git a/pom.xml b/pom.xml index f91cd79fbc865989163056b9f0685c8cb157de57..955c93f1ae13df87c9e456b3d53357b13969e9fd 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ org.opengroup.osdu core-lib-azure jar - 0.0.21 + 0.0.22 core-lib-azure diff --git a/src/main/java/org/opengroup/osdu/azure/blobstorage/BlobStore.java b/src/main/java/org/opengroup/osdu/azure/blobstorage/BlobStore.java index cf88d09af438db950fc79f1b13198da724e4f10b..fb2748f3f6fa3b38bad4f3a65cc61b3dbc433045 100644 --- a/src/main/java/org/opengroup/osdu/azure/blobstorage/BlobStore.java +++ b/src/main/java/org/opengroup/osdu/azure/blobstorage/BlobStore.java @@ -15,6 +15,7 @@ package org.opengroup.osdu.azure.blobstorage; import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; import com.azure.storage.blob.models.BlobErrorCode; import com.azure.storage.blob.models.BlobStorageException; import com.azure.storage.blob.specialized.BlockBlobClient; @@ -55,6 +56,23 @@ import java.util.Collections;; * { * Boolean success = blobStorage.deleteFromBlob("dataPartitionId", "filePath"); * } + * + * String readFromStorageContainerExamole() + * { + * String content = blobStorage.readFromStorageContainer("dataPartitionId", "filePath", "containerName"); + * if (content != null) + * return content; + * } + * + * void writeToStorageContainerExample() + * { + * blobStorage.writeToStorageContainer("dataPartitionId", "filePath", "content", "containerName"); + * } + * + * void deleteFromStorageContainerExample() + * { + * Boolean success = blobStorage.deleteFromStorageContainer("dataPartitionId", "filePath", "containerName"); + * } * } * */ @@ -66,6 +84,9 @@ public class BlobStore { @Autowired private IBlobContainerClientFactory blobContainerClientFactory; + @Autowired + private IBlobServiceClientFactory blobServiceClientFactory; + @Autowired private ILogger logger; @@ -136,8 +157,8 @@ public class BlobStore { * @param dataPartitionId Data partition id */ public void writeToBlob(final String dataPartitionId, - final String filePath, - final String content) { + final String filePath, + final String content) { byte[] bytes = content.getBytes(StandardCharsets.UTF_8); int bytesSize = bytes.length; BlobContainerClient blobContainerClient = getBlobContainerClient(dataPartitionId); @@ -155,6 +176,102 @@ public class BlobStore { } } + /** + * + * @param filePath Path of file to be read. + * @param dataPartitionId Data partition id + * @param containerName Name of the storage container + * @return the content of file with provided file path. + */ + public String readFromStorageContainer( + final String dataPartitionId, + final String filePath, + final String containerName) { + BlobContainerClient blobContainerClient = getBlobContainerClient(dataPartitionId, containerName); + BlockBlobClient blockBlobClient = blobContainerClient.getBlobClient(filePath).getBlockBlobClient(); + try (ByteArrayOutputStream downloadStream = new ByteArrayOutputStream()) { + blockBlobClient.download(downloadStream); + return downloadStream.toString(StandardCharsets.UTF_8.name()); + } catch (BlobStorageException ex) { + if (ex.getErrorCode().equals(BlobErrorCode.BLOB_NOT_FOUND)) { + String errorMessage = "Specified blob was not found"; + logger.warning(LOG_PREFIX, errorMessage, Collections.emptyMap()); + throw new AppException(404, errorMessage, ex.getMessage(), ex); + } else { + String errorMessage = "Failed to read specified blob"; + logger.warning(LOG_PREFIX, errorMessage, Collections.emptyMap()); + throw new AppException(500, errorMessage, ex.getMessage(), ex); + } + } catch (UnsupportedEncodingException ex) { + String errorMessage = String.format("Encoding was not correct for item with name=%s", filePath); + logger.warning(LOG_PREFIX, errorMessage, Collections.emptyMap()); + throw new AppException(400, errorMessage, ex.getMessage(), ex); + } catch (IOException ex) { + String errorMessage = String.format("Malformed document for item with name=%s", filePath); + logger.warning(LOG_PREFIX, errorMessage, Collections.emptyMap()); + throw new AppException(500, errorMessage, ex.getMessage(), ex); + } + } + + /** + * + * @param filePath Path of file to be deleted. + * @param dataPartitionId Data partition id + * @param containerName Name of the storage container + * @return boolean indicating whether the deletion of given file was successful or not. + */ + public boolean deleteFromStorageContainer( + final String dataPartitionId, + final String filePath, + final String containerName) { + BlobContainerClient blobContainerClient = getBlobContainerClient(dataPartitionId, containerName); + BlockBlobClient blockBlobClient = blobContainerClient.getBlobClient(filePath).getBlockBlobClient(); + try { + blockBlobClient.delete(); + return true; + } catch (BlobStorageException ex) { + if (ex.getErrorCode().equals(BlobErrorCode.BLOB_NOT_FOUND)) { + String errorMessage = "Specified blob was not found"; + logger.warning(LOG_PREFIX, errorMessage, Collections.emptyMap()); + throw new AppException(404, errorMessage, ex.getMessage(), ex); + } else { + String errorMessage = "Failed to delete blob"; + logger.warning(LOG_PREFIX, errorMessage, Collections.emptyMap()); + throw new AppException(500, errorMessage, ex.getMessage(), ex); + } + } + } + + /** + * + * @param filePath Path of file to be written at. + * @param content Content to be written in the file. + * @param dataPartitionId Data partition id + * @param containerName Name of the storage container + */ + public void writeToStorageContainer( + final String dataPartitionId, + final String filePath, + final String content, + final String containerName) { + byte[] bytes = content.getBytes(StandardCharsets.UTF_8); + int bytesSize = bytes.length; + BlobContainerClient blobContainerClient = getBlobContainerClient(dataPartitionId, containerName); + BlockBlobClient blockBlobClient = blobContainerClient.getBlobClient(filePath).getBlockBlobClient(); + try (ByteArrayInputStream dataStream = new ByteArrayInputStream(bytes)) { + blockBlobClient.upload(dataStream, bytesSize, true); + } catch (BlobStorageException ex) { + String errorMessage = "Failed to upload file content."; + logger.warning(LOG_PREFIX, errorMessage, Collections.emptyMap()); + throw new AppException(500, errorMessage, ex.getMessage(), ex); + } catch (IOException ex) { + String errorMessage = String.format("Malformed document for item with name=%s", filePath); + logger.warning(LOG_PREFIX, errorMessage, Collections.emptyMap()); + throw new AppException(500, errorMessage, ex.getMessage(), ex); + } + } + + /** * * @param dataPartitionId Data partition id @@ -169,5 +286,22 @@ public class BlobStore { throw new AppException(500, errorMessage, ex.getMessage(), ex); } } -} + /** + * + * @param dataPartitionId Data partition id. + * @param containerName Name of storage container. + * @return blob container client corresponding to the dataPartitionId. + */ + private BlobContainerClient getBlobContainerClient(final String dataPartitionId, final String containerName) { + try { + BlobServiceClient serviceClient = blobServiceClientFactory.getBlobServiceClient(dataPartitionId); + return serviceClient.getBlobContainerClient(containerName); + } catch (Exception ex) { + String errorMessage = "Error creating creating blob container client."; + logger.warning(LOG_PREFIX, errorMessage, Collections.emptyMap()); + throw new AppException(500, errorMessage, ex.getMessage(), ex); + } + } + +} diff --git a/src/test/java/org/opengroup/osdu/azure/blobstorage/BlobStoreTest.java b/src/test/java/org/opengroup/osdu/azure/blobstorage/BlobStoreTest.java index 453367ae50d18744888cafb457f2623b6493e9a7..b7c1f53c70b97bea1a5b4e8b3cac540d686f9813 100644 --- a/src/test/java/org/opengroup/osdu/azure/blobstorage/BlobStoreTest.java +++ b/src/test/java/org/opengroup/osdu/azure/blobstorage/BlobStoreTest.java @@ -16,6 +16,7 @@ package org.opengroup.osdu.azure.blobstorage; import com.azure.storage.blob.BlobClient; import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; import com.azure.storage.blob.models.BlobErrorCode; import com.azure.storage.blob.models.BlobStorageException; import com.azure.storage.blob.models.BlockBlobItem; @@ -44,6 +45,7 @@ public class BlobStoreTest { private static final String PARTITION_ID = "dataPartitionId"; private static final String FILE_PATH = "filePath"; private static final String CONTENT = "hello world"; + private static final String STORAGE_CONTAINER_NAME = "containerName"; @InjectMocks BlobStore blobStore; @@ -51,6 +53,12 @@ public class BlobStoreTest { @Mock IBlobContainerClientFactory blobContainerClientFactory; + @Mock + IBlobServiceClientFactory blobServiceClientFactory; + + @Mock + BlobServiceClient blobServiceClient; + @Mock BlobContainerClient blobContainerClient; @@ -69,9 +77,11 @@ public class BlobStoreTest { @BeforeEach void init() { initMocks(this); - lenient().doReturn(blobContainerClient).when(blobContainerClientFactory).getClient(PARTITION_ID); - lenient().doReturn(blobClient).when(blobContainerClient).getBlobClient(FILE_PATH); - lenient().doReturn(blockBlobClient).when(blobClient).getBlockBlobClient(); + lenient().when(blobClient.getBlockBlobClient()).thenReturn(blockBlobClient); + lenient().when(blobContainerClient.getBlobClient(FILE_PATH)).thenReturn(blobClient); + lenient().when(blobServiceClient.getBlobContainerClient(STORAGE_CONTAINER_NAME)).thenReturn(blobContainerClient); + lenient().when(blobServiceClientFactory.getBlobServiceClient(PARTITION_ID)).thenReturn(blobServiceClient); + lenient().when(blobContainerClientFactory.getClient(PARTITION_ID)).thenReturn(blobContainerClient); lenient().doNothing().when(logger).warning(eq("azure-core-lib"), any(), anyMap()); } @@ -216,6 +226,160 @@ public class BlobStoreTest { } } + @Test + public void readFromStorageContainer_ErrorCreatingBlobContainerClient() + { + doThrow(AppException.class).when(blobServiceClientFactory).getBlobServiceClient(eq(PARTITION_ID)); + try { + String content = blobStore.readFromStorageContainer(PARTITION_ID, FILE_PATH, STORAGE_CONTAINER_NAME); + } catch (AppException ex) { + assertEquals(500, ex.getError().getCode()); + } catch (Exception ex) { + fail("should not get different error code"); + } + } + + @Test + public void readFromStorageContainer_ErrorCreatingBlobContainerClient_FromServiceClient() + { + doThrow(AppException.class).when(blobServiceClient).getBlobContainerClient(eq(STORAGE_CONTAINER_NAME)); + try { + String content = blobStore.readFromStorageContainer(PARTITION_ID, FILE_PATH, STORAGE_CONTAINER_NAME); + } catch (AppException ex) { + assertEquals(500, ex.getError().getCode()); + } catch (Exception ex) { + fail("should not get different error code"); + } + } + + @Test + public void readFromStorageContainer_Success() + { + String content = blobStore.readFromStorageContainer(PARTITION_ID, FILE_PATH, STORAGE_CONTAINER_NAME); + ArgumentCaptor outputStream = ArgumentCaptor.forClass(ByteArrayOutputStream.class); + + // validate that the download method is being invoked appropriately. + verify(blockBlobClient).download(outputStream.capture()); + } + + @Test + public void readFromStorageContainer_BlobNotFound() + { + BlobStorageException exception = mockStorageException(BlobErrorCode.BLOB_NOT_FOUND); + doThrow(exception).when(blockBlobClient).download(any()); + try { + String content = blobStore.readFromStorageContainer(PARTITION_ID, FILE_PATH, STORAGE_CONTAINER_NAME); + } catch (AppException ex) { + assertEquals(404, ex.getError().getCode()); + } catch (Exception ex) { + fail("should not get different error code"); + } + } + + @Test + public void readFromStorageContainer_InternalError() + { + BlobStorageException exception = mockStorageException(BlobErrorCode.INTERNAL_ERROR); + doThrow(exception).when(blockBlobClient).download(any()); + try { + String content = blobStore.readFromStorageContainer(PARTITION_ID, FILE_PATH, STORAGE_CONTAINER_NAME); + } catch (AppException ex) { + assertEquals(500, ex.getError().getCode()); + } catch (Exception ex) { + fail("should not get different error code"); + } + } + + @Test + public void deleteFromStorageContainer_ErrorCreatingBlobContainerClient() + { + doThrow(AppException.class).when(blobServiceClientFactory).getBlobServiceClient(eq(PARTITION_ID)); + try { + blobStore.deleteFromStorageContainer(PARTITION_ID, FILE_PATH, STORAGE_CONTAINER_NAME); + } catch (AppException ex) { + assertEquals(500, ex.getError().getCode()); + } catch (Exception ex) { + fail("should not get different error code"); + } + } + + @Test + public void deleteFromStorageContainer_BlobNotFound() + { + BlobStorageException exception = mockStorageException(BlobErrorCode.BLOB_NOT_FOUND); + doThrow(exception).when(blockBlobClient).delete(); + try { + blobStore.deleteFromStorageContainer(PARTITION_ID, FILE_PATH, STORAGE_CONTAINER_NAME); + } catch (AppException ex) { + assertEquals(404, ex.getError().getCode()); + } catch (Exception ex) { + fail("should not get different error code"); + } + } + + @Test + public void deleteFromStorageContainer_InternalError() + { + BlobStorageException exception = mockStorageException(BlobErrorCode.INTERNAL_ERROR); + doThrow(exception).when(blockBlobClient).delete(); + try { + blobStore.deleteFromStorageContainer(PARTITION_ID, FILE_PATH, STORAGE_CONTAINER_NAME); + } catch (AppException ex) { + assertEquals(500, ex.getError().getCode()); + } catch (Exception ex) { + fail("should not get different error code"); + } + } + + @Test + public void deleteFromStorageContainer_Success() + { + doNothing().when(blockBlobClient).delete(); + try { + blobStore.deleteFromStorageContainer(PARTITION_ID, FILE_PATH, STORAGE_CONTAINER_NAME); + } catch (Exception ex) { + fail("should not get any exception."); + } + } + + @Test + public void writeToStorageContainer_ErrorCreatingBlobContainerClient() + { + doThrow(AppException.class).when(blobServiceClientFactory).getBlobServiceClient(eq(PARTITION_ID)); + try { + blobStore.writeToStorageContainer(PARTITION_ID, FILE_PATH, CONTENT, STORAGE_CONTAINER_NAME); + } catch (AppException ex) { + assertEquals(500, ex.getError().getCode()); + } catch (Exception ex) { + fail("should not get different error code"); + } + } + + @Test + public void writeToStorageContainer_InternalError() + { + BlobStorageException exception = mockStorageException(BlobErrorCode.INTERNAL_ERROR); + doThrow(exception).when(blockBlobClient).upload(any(), anyLong(), eq(true)); + try { + blobStore.writeToStorageContainer(PARTITION_ID, FILE_PATH, CONTENT, STORAGE_CONTAINER_NAME); + } catch (AppException ex) { + assertEquals(500, ex.getError().getCode()); + } catch (Exception ex) { + fail("should not get different error code"); + } + } + + @Test + public void writeToStorageContainer_Success() + { + doReturn(blockBlobItem).when(blockBlobClient).upload(any(), anyLong(), eq(true)); + try { + blobStore.writeToStorageContainer(PARTITION_ID, FILE_PATH, CONTENT, STORAGE_CONTAINER_NAME); + } catch (Exception ex) { + fail("should not get any exception."); + } + } + private BlobStorageException mockStorageException(BlobErrorCode errorCode) { BlobStorageException mockException = mock(BlobStorageException.class); lenient().when(mockException.getErrorCode()).thenReturn(errorCode);