From 384e71ae1b9f56795b4ec2f1c48608c4fdda98da Mon Sep 17 00:00:00 2001 From: jhonatapers Date: Wed, 27 Aug 2025 22:20:17 -0300 Subject: [PATCH] feat: enhance file handling with storage key management and retrieval functionality --- .../get/DefaultGetFileContentUseCase.java | 12 +++++-- .../content/get/GetFileContentOutput.java | 8 +++-- .../file/create/DefaultCreateFileUseCase.java | 26 +++++++++++---- .../file/delete/DefaultDeleteFileUseCase.java | 2 +- .../get/DefaultGetFileContentUseCaseTest.java | 13 ++++++-- .../create/DefaultCreateFileUseCaseTest.java | 29 ++++++++-------- .../delete/DefaultDeleteFileUseCaseTest.java | 2 +- .../StorageKeyAlreadyExistsException.java | 16 +++++++++ .../com/callv2/drive/domain/file/Content.java | 16 ++++----- .../drive/domain/storage/StorageService.java | 6 ++-- .../callv2/drive/domain/file/FileTest.java | 4 +-- .../api/controller/FileController.java | 6 ++-- .../usecase/FileUseCaseConfig.java | 2 +- .../file/persistence/FileJpaEntity.java | 20 +++++------ .../storage/FileSystemStorageService.java | 33 +++++++++++++++---- 15 files changed, 132 insertions(+), 63 deletions(-) create mode 100644 domain/src/main/java/com/callv2/drive/domain/exception/StorageKeyAlreadyExistsException.java diff --git a/application/src/main/java/com/callv2/drive/application/file/content/get/DefaultGetFileContentUseCase.java b/application/src/main/java/com/callv2/drive/application/file/content/get/DefaultGetFileContentUseCase.java index 0cf241fd..15bd11c6 100644 --- a/application/src/main/java/com/callv2/drive/application/file/content/get/DefaultGetFileContentUseCase.java +++ b/application/src/main/java/com/callv2/drive/application/file/content/get/DefaultGetFileContentUseCase.java @@ -6,13 +6,18 @@ import com.callv2.drive.domain.file.File; import com.callv2.drive.domain.file.FileGateway; import com.callv2.drive.domain.file.FileID; +import com.callv2.drive.domain.storage.StorageService; public class DefaultGetFileContentUseCase extends GetFileContentUseCase { private final FileGateway fileGateway; + private final StorageService storageService; - public DefaultGetFileContentUseCase(final FileGateway fileGateway) { + public DefaultGetFileContentUseCase( + final FileGateway fileGateway, + final StorageService storageService) { this.fileGateway = Objects.requireNonNull(fileGateway); + this.storageService = Objects.requireNonNull(storageService); } @Override @@ -26,8 +31,9 @@ public GetFileContentOutput execute(GetFileContentInput input) { return GetFileContentOutput.with( file.getName().value(), - file.getContent().location(), - file.getContent().size()); + file.getContent().size(), + storageService.retrieve(file.getContent().storageKey())); + } } diff --git a/application/src/main/java/com/callv2/drive/application/file/content/get/GetFileContentOutput.java b/application/src/main/java/com/callv2/drive/application/file/content/get/GetFileContentOutput.java index af37a384..451102a7 100644 --- a/application/src/main/java/com/callv2/drive/application/file/content/get/GetFileContentOutput.java +++ b/application/src/main/java/com/callv2/drive/application/file/content/get/GetFileContentOutput.java @@ -1,9 +1,11 @@ package com.callv2.drive.application.file.content.get; -public record GetFileContentOutput(String name, String location, long size) { +import java.io.InputStream; - public static GetFileContentOutput with(final String name, final String location, final long size) { - return new GetFileContentOutput(name, location, size); +public record GetFileContentOutput(String name, long size, InputStream inputStream) { + + public static GetFileContentOutput with(final String name, final long size, InputStream inputStream) { + return new GetFileContentOutput(name, size, inputStream); } } diff --git a/application/src/main/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCase.java b/application/src/main/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCase.java index 26fc101a..77dcfe77 100644 --- a/application/src/main/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCase.java +++ b/application/src/main/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCase.java @@ -8,6 +8,7 @@ import com.callv2.drive.domain.exception.InternalErrorException; import com.callv2.drive.domain.exception.NotFoundException; import com.callv2.drive.domain.exception.QuotaExceededException; +import com.callv2.drive.domain.exception.StorageKeyAlreadyExistsException; import com.callv2.drive.domain.exception.ValidationException; import com.callv2.drive.domain.file.Content; import com.callv2.drive.domain.file.File; @@ -77,12 +78,11 @@ public CreateFileOutput execute(final CreateFileInput input) { throw ValidationException.with("Could not create Aggregate File", ValidationError.with("File with same name already exists on this folder")); - final String randomContentName = UUID.randomUUID().toString(); - final String contentLocation = storeContentFile(randomContentName, input.content()); + final String storageKey = storeContentFile(input.content()); final String contentType = input.contentType(); final Long contentSize = input.size(); - final Content content = Content.of(contentLocation, contentType, contentSize); + final Content content = Content.of(storageKey, contentType, contentSize); final File file = notification.validate(() -> File.create(ownerId, folderId, fileName, content)); if (notification.hasError()) @@ -97,17 +97,31 @@ private void storeFile(final File file) { try { fileGateway.create(file); } catch (Exception e) { - deleteContentFile(file.getContent().location()); + deleteContentFile(file.getContent().storageKey()); throw InternalErrorException.with("Could not store File", e); } } - private String storeContentFile(final String contentName, final InputStream inputStream) { + private String storeContentFile(final InputStream inputStream) { + try { - return storageService.store(contentName, inputStream); + + do { + + try { + final String key = UUID.randomUUID().toString(); + storageService.store(key, inputStream); + return key; + } catch (StorageKeyAlreadyExistsException e) { + continue; + } + + } while (true); + } catch (Exception e) { throw InternalErrorException.with("Could not store BinaryContent", e); } + } private void deleteContentFile(final String contentLocation) { diff --git a/application/src/main/java/com/callv2/drive/application/file/delete/DefaultDeleteFileUseCase.java b/application/src/main/java/com/callv2/drive/application/file/delete/DefaultDeleteFileUseCase.java index a5279c75..a68834bd 100644 --- a/application/src/main/java/com/callv2/drive/application/file/delete/DefaultDeleteFileUseCase.java +++ b/application/src/main/java/com/callv2/drive/application/file/delete/DefaultDeleteFileUseCase.java @@ -43,7 +43,7 @@ public void execute(final DeleteFileInput input) { .orElseThrow(() -> NotFoundException.with(File.class, input.fileId().toString())); fileGateway.deleteById(file.getId()); // TODO maybe needs transactional - deleteContentFile(file.getContent().location()); + deleteContentFile(file.getContent().storageKey()); } private void deleteContentFile(final String contentLocation) { diff --git a/application/src/test/java/com/callv2/drive/application/file/content/get/DefaultGetFileContentUseCaseTest.java b/application/src/test/java/com/callv2/drive/application/file/content/get/DefaultGetFileContentUseCaseTest.java index fa228678..ef38da4e 100644 --- a/application/src/test/java/com/callv2/drive/application/file/content/get/DefaultGetFileContentUseCaseTest.java +++ b/application/src/test/java/com/callv2/drive/application/file/content/get/DefaultGetFileContentUseCaseTest.java @@ -8,6 +8,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.io.ByteArrayInputStream; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -24,6 +25,7 @@ import com.callv2.drive.domain.file.FileName; import com.callv2.drive.domain.folder.FolderID; import com.callv2.drive.domain.member.MemberID; +import com.callv2.drive.domain.storage.StorageService; @ExtendWith(MockitoExtension.class) public class DefaultGetFileContentUseCaseTest { @@ -34,26 +36,33 @@ public class DefaultGetFileContentUseCaseTest { @Mock FileGateway fileGateway; + @Mock + StorageService storageService; + @Test void givenAValidParam_whenCallsExecute_shouldReturnContent() { final var ownerId = MemberID.of("owner"); final var expectedFolder = FolderID.unique(); final var expectedFileName = FileName.of("file.txt"); - final var expectedContent = Content.of("location", "text", 10); + final var expectedContent = Content.of("key", "text", 10); final var expectedFile = File.create(ownerId, expectedFolder, expectedFileName, expectedContent); final var expectedFileId = expectedFile.getId(); + final var expectedInputStream = new ByteArrayInputStream(new byte[] {}); when(fileGateway.findById(any())) .thenReturn(Optional.of(expectedFile)); + when(storageService.retrieve(expectedContent.storageKey())) + .thenReturn(expectedInputStream); + final var input = GetFileContentInput.with(expectedFileId.getValue()); final var actualOutput = useCase.execute(input); assertEquals(expectedFileName.value(), actualOutput.name()); - assertEquals(expectedContent.location(), actualOutput.location()); assertEquals(expectedContent.size(), actualOutput.size()); + assertEquals(expectedInputStream, actualOutput.inputStream()); verify(fileGateway, times(1)).findById(any()); verify(fileGateway, times(1)).findById(eq(expectedFileId)); diff --git a/application/src/test/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCaseTest.java b/application/src/test/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCaseTest.java index 4a555b42..81525f0c 100644 --- a/application/src/test/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCaseTest.java +++ b/application/src/test/java/com/callv2/drive/application/file/create/DefaultCreateFileUseCaseTest.java @@ -45,7 +45,7 @@ import com.callv2.drive.domain.storage.StorageService; @ExtendWith(MockitoExtension.class) -public class DefaultCreateFileUseCaseTest { +class DefaultCreateFileUseCaseTest { @InjectMocks DefaultCreateFileUseCase useCase; @@ -99,8 +99,8 @@ void givenAValidParams_whenCallsExecute_thenShouldCreateFile() { when(folderGateway.findById(any())) .thenReturn(Optional.of(folder)); - when(storageService.store(any(), any())) - .then(returnsFirstArg()); + doNothing() + .when(storageService).store(any(), any()); when(fileGateway.create(any())) .thenAnswer(returnsFirstArg()); @@ -132,7 +132,7 @@ void givenAValidParams_whenCallsExecute_thenShouldCreateFile() { assertEquals(expectedContentType, file.getContent().type()); assertNotNull(file.getCreatedAt()); assertNotNull(file.getUpdatedAt()); - assertNotNull(file.getContent().location()); + assertNotNull(file.getContent().storageKey()); assertEquals(file.getCreatedAt(), file.getUpdatedAt()); return true; @@ -356,8 +356,8 @@ void givenAValidParams_whenCallsExecuteAndFileGatewayCreateThrowsRandomException when(folderGateway.findById(any())) .thenReturn(Optional.of(folder)); - when(storageService.store(any(), any())) - .then(returnsFirstArg()); + doNothing() + .when(storageService).store(any(), any()); when(fileGateway.create(any())) .thenThrow(new IllegalStateException("FileGateway Exception")); @@ -394,7 +394,7 @@ void givenAValidParams_whenCallsExecuteAndFileGatewayCreateThrowsRandomException assertEquals(expectedContentType, file.getContent().type()); assertNotNull(file.getCreatedAt()); assertNotNull(file.getUpdatedAt()); - assertNotNull(file.getContent().location()); + assertNotNull(file.getContent().storageKey()); assertEquals(file.getCreatedAt(), file.getUpdatedAt()); return true; @@ -441,8 +441,8 @@ void givenAValidParams_whenCallsExecuteAndFileGatewayCreateAndContentGatewayDele when(folderGateway.findById(any())) .thenReturn(Optional.of(folder)); - when(storageService.store(any(), any())) - .then(returnsFirstArg()); + doNothing() + .when(storageService).store(any(), any()); when(fileGateway.create(any())) .thenThrow(new IllegalStateException("FileGateway Exception")); @@ -479,7 +479,7 @@ void givenAValidParams_whenCallsExecuteAndFileGatewayCreateAndContentGatewayDele assertEquals(expectedContentType, file.getContent().type()); assertNotNull(file.getCreatedAt()); assertNotNull(file.getUpdatedAt()); - assertNotNull(file.getContent().location()); + assertNotNull(file.getContent().storageKey()); assertEquals(file.getCreatedAt(), file.getUpdatedAt()); return true; @@ -523,8 +523,9 @@ void givenAValidParams_whenCallsExecuteAndContentGatewayStoreThrowsRandomExcepti when(folderGateway.findById(any())) .thenReturn(Optional.of(folder)); - when(storageService.store(any(), any())) - .thenThrow(new IllegalStateException("ContentGateway Exception")); + doThrow(new IllegalStateException("ContentGateway Exception")) + .when(storageService) + .store(any(), any()); final var input = CreateFileInput.of( ownerId.getValue(), @@ -709,8 +710,8 @@ void givenAnInvalidParamsWithContentTypeNull_whenCallsExecute_thenShouldThrowsVa when(folderGateway.findById(any())) .thenReturn(Optional.of(folder)); - when(storageService.store(any(), any())) - .then(returnsFirstArg()); + doNothing() + .when(storageService).store(any(), any()); final var input = CreateFileInput.of( ownerId.getValue(), diff --git a/application/src/test/java/com/callv2/drive/application/file/delete/DefaultDeleteFileUseCaseTest.java b/application/src/test/java/com/callv2/drive/application/file/delete/DefaultDeleteFileUseCaseTest.java index d121b867..c8b998e0 100644 --- a/application/src/test/java/com/callv2/drive/application/file/delete/DefaultDeleteFileUseCaseTest.java +++ b/application/src/test/java/com/callv2/drive/application/file/delete/DefaultDeleteFileUseCaseTest.java @@ -105,7 +105,7 @@ void givenAValidParam_whenCallsExecute_thenShouldDeleteFile() { verify(fileGateway, times(1)).deleteById(any()); verify(fileGateway, times(1)).deleteById(eq(expectedFileId)); verify(storageService, times(1)).delete(any()); - verify(storageService, times(1)).delete(eq(expectedContent.location())); + verify(storageService, times(1)).delete(eq(expectedContent.storageKey())); } @Test diff --git a/domain/src/main/java/com/callv2/drive/domain/exception/StorageKeyAlreadyExistsException.java b/domain/src/main/java/com/callv2/drive/domain/exception/StorageKeyAlreadyExistsException.java new file mode 100644 index 00000000..db46c2f2 --- /dev/null +++ b/domain/src/main/java/com/callv2/drive/domain/exception/StorageKeyAlreadyExistsException.java @@ -0,0 +1,16 @@ +package com.callv2.drive.domain.exception; + +import java.util.List; + +public class StorageKeyAlreadyExistsException extends SilentDomainException { + + private StorageKeyAlreadyExistsException(final String key) { + super("storage key [%s] already exists.".formatted(key), + List.of(DomainException.Error.with("storage key [%s] already exists.".formatted(key)))); + } + + public static StorageKeyAlreadyExistsException with(final String key) { + return new StorageKeyAlreadyExistsException(key); + } + +} diff --git a/domain/src/main/java/com/callv2/drive/domain/file/Content.java b/domain/src/main/java/com/callv2/drive/domain/file/Content.java index b63b1b6e..8e270282 100644 --- a/domain/src/main/java/com/callv2/drive/domain/file/Content.java +++ b/domain/src/main/java/com/callv2/drive/domain/file/Content.java @@ -4,25 +4,25 @@ import com.callv2.drive.domain.validation.ValidationHandler; import com.callv2.drive.domain.validation.ValidationError; -public record Content(String location, String type, long size) implements ValueObject { +public record Content(String storageKey, String type, long size) implements ValueObject { - public static Content of(final String location, final String type, final long size) { - return new Content(location, type, size); + public static Content of(final String storageKey, final String type, final long size) { + return new Content(storageKey, type, size); } @Override public void validate(final ValidationHandler aHandler) { - validateLocation(aHandler); + validateStorageKey(aHandler); validateType(aHandler); } - private void validateLocation(final ValidationHandler aHandler) { - if (location == null) { - aHandler.append(new ValidationError("'location' cannot be null.")); + private void validateStorageKey(final ValidationHandler aHandler) { + if (storageKey == null) { + aHandler.append(new ValidationError("'storageKey' cannot be null.")); return; } - if (location.trim().isEmpty()) { + if (storageKey.trim().isEmpty()) { aHandler.append(new ValidationError("'location' cannot be empty.")); return; } diff --git a/domain/src/main/java/com/callv2/drive/domain/storage/StorageService.java b/domain/src/main/java/com/callv2/drive/domain/storage/StorageService.java index 63419902..470449af 100644 --- a/domain/src/main/java/com/callv2/drive/domain/storage/StorageService.java +++ b/domain/src/main/java/com/callv2/drive/domain/storage/StorageService.java @@ -4,8 +4,10 @@ public interface StorageService { - String store(String name, InputStream content); + void store(String key, InputStream content); - void delete(String name); + void delete(String key); + + InputStream retrieve(String key); } diff --git a/domain/src/test/java/com/callv2/drive/domain/file/FileTest.java b/domain/src/test/java/com/callv2/drive/domain/file/FileTest.java index ae1f9414..7540c2c6 100644 --- a/domain/src/test/java/com/callv2/drive/domain/file/FileTest.java +++ b/domain/src/test/java/com/callv2/drive/domain/file/FileTest.java @@ -35,7 +35,7 @@ void givenAValidParams_whenCallsCreate_thenShouldCreateFile() { assertEquals(expectedOwner, actualFile.getOwner()); assertEquals(expectedName, actualFile.getName().value()); assertEquals(expectedContentType, actualFile.getContent().type()); - assertEquals(expectedContentLocation, actualFile.getContent().location()); + assertEquals(expectedContentLocation, actualFile.getContent().storageKey()); assertEquals(expectedContentSize, actualFile.getContent().size()); assertNotNull(actualFile.getCreatedAt()); assertNotNull(actualFile.getUpdatedAt()); @@ -218,7 +218,7 @@ void givenAValidParams_whenCallsUpdate_thenShouldCreateFile() { assertEquals(expectedName, actualUpdatedFile.getName()); assertEquals(expectedContentType, actualUpdatedFile.getContent().type()); - assertEquals(expectedContentLocation, actualUpdatedFile.getContent().location()); + assertEquals(expectedContentLocation, actualUpdatedFile.getContent().storageKey()); assertEquals(expectedContentSize, actualUpdatedFile.getContent().size()); assertNotNull(actualUpdatedFile.getCreatedAt()); assertNotNull(actualUpdatedFile.getUpdatedAt()); diff --git a/infrastructure/src/main/java/com/callv2/drive/infrastructure/api/controller/FileController.java b/infrastructure/src/main/java/com/callv2/drive/infrastructure/api/controller/FileController.java index e7a6db47..015c3d26 100644 --- a/infrastructure/src/main/java/com/callv2/drive/infrastructure/api/controller/FileController.java +++ b/infrastructure/src/main/java/com/callv2/drive/infrastructure/api/controller/FileController.java @@ -4,7 +4,7 @@ import java.util.List; import java.util.UUID; -import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -17,8 +17,8 @@ import com.callv2.drive.application.file.content.get.GetFileContentOutput; import com.callv2.drive.application.file.content.get.GetFileContentUseCase; import com.callv2.drive.application.file.create.CreateFileUseCase; -import com.callv2.drive.application.file.delete.DeleteFileUseCase; import com.callv2.drive.application.file.delete.DeleteFileInput; +import com.callv2.drive.application.file.delete.DeleteFileUseCase; import com.callv2.drive.application.file.retrieve.get.GetFileInput; import com.callv2.drive.application.file.retrieve.get.GetFileUseCase; import com.callv2.drive.application.file.retrieve.list.ListFilesUseCase; @@ -95,7 +95,7 @@ public ResponseEntity download(UUID id) { .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + output.name() + "\"") .contentLength(output.size()) .contentType(MediaType.APPLICATION_OCTET_STREAM) - .body(new FileSystemResource(output.location())); + .body(new InputStreamResource(output.inputStream())); } @Override diff --git a/infrastructure/src/main/java/com/callv2/drive/infrastructure/configuration/usecase/FileUseCaseConfig.java b/infrastructure/src/main/java/com/callv2/drive/infrastructure/configuration/usecase/FileUseCaseConfig.java index 030eb267..ebd1f55e 100644 --- a/infrastructure/src/main/java/com/callv2/drive/infrastructure/configuration/usecase/FileUseCaseConfig.java +++ b/infrastructure/src/main/java/com/callv2/drive/infrastructure/configuration/usecase/FileUseCaseConfig.java @@ -54,7 +54,7 @@ GetFileUseCase getFileUseCase() { @Bean GetFileContentUseCase getFileContentUseCase() { - return new DefaultGetFileContentUseCase(fileGateway); + return new DefaultGetFileContentUseCase(fileGateway, storageService); } @Bean diff --git a/infrastructure/src/main/java/com/callv2/drive/infrastructure/file/persistence/FileJpaEntity.java b/infrastructure/src/main/java/com/callv2/drive/infrastructure/file/persistence/FileJpaEntity.java index bdb06259..43f45798 100644 --- a/infrastructure/src/main/java/com/callv2/drive/infrastructure/file/persistence/FileJpaEntity.java +++ b/infrastructure/src/main/java/com/callv2/drive/infrastructure/file/persistence/FileJpaEntity.java @@ -34,8 +34,8 @@ public class FileJpaEntity { @Column(name = "content_type", nullable = false) private String contentType; - @Column(name = "content_location", nullable = false) - private String contentLocation; + @Column(name = "content_storage_key", nullable = false) + private String contentStorageKey; @Column(name = "content_size", nullable = false) private Long contentSize; @@ -61,7 +61,7 @@ private FileJpaEntity( this.folderId = folderId; this.name = name; this.contentType = contentType; - this.contentLocation = contentLocation; + this.contentStorageKey = contentLocation; this.contentSize = contentSize; this.createdAt = createdAt; this.updatedAt = updatedAt; @@ -77,7 +77,7 @@ public static FileJpaEntity from(final File file) { file.getFolder().getValue(), file.getName().value(), file.getContent().type(), - file.getContent().location(), + file.getContent().storageKey(), file.getContent().size(), file.getCreatedAt(), file.getUpdatedAt()); @@ -89,7 +89,7 @@ public File toDomain() { MemberID.of(getOwnerId()), FolderID.of(getFolderId()), FileName.of(getName()), - Content.of(getContentLocation(), getContentType(), getContentSize()), + Content.of(getContentStorageKey(), getContentType(), getContentSize()), getCreatedAt(), getUpdatedAt()); } @@ -134,12 +134,12 @@ public void setContentType(String contentType) { this.contentType = contentType; } - public String getContentLocation() { - return contentLocation; + public String getContentStorageKey() { + return contentStorageKey; } - public void setContentLocation(String contentLocation) { - this.contentLocation = contentLocation; + public void setContentStorageKey(String contentStorageKey) { + this.contentStorageKey = contentStorageKey; } public Long getContentSize() { @@ -169,7 +169,7 @@ public void setUpdatedAt(Instant updatedAt) { @Override public String toString() { return "FileJpaEntity [id=" + id + ", ownerId=" + ownerId + ", folderId=" + folderId + ", name=" + name - + ", contentType=" + contentType + ", contentLocation=" + contentLocation + ", contentSize=" + + ", contentType=" + contentType + ", contentStorageKey=" + contentStorageKey + ", contentSize=" + contentSize + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + "]"; } diff --git a/infrastructure/src/main/java/com/callv2/drive/infrastructure/storage/FileSystemStorageService.java b/infrastructure/src/main/java/com/callv2/drive/infrastructure/storage/FileSystemStorageService.java index a16b4127..9f33cbe4 100644 --- a/infrastructure/src/main/java/com/callv2/drive/infrastructure/storage/FileSystemStorageService.java +++ b/infrastructure/src/main/java/com/callv2/drive/infrastructure/storage/FileSystemStorageService.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.InputStream; +import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -9,24 +10,28 @@ import java.util.Objects; import com.callv2.drive.domain.exception.InternalErrorException; +import com.callv2.drive.domain.exception.StorageKeyAlreadyExistsException; import com.callv2.drive.domain.storage.StorageService; public class FileSystemStorageService implements StorageService { private final Path rootLocation; - public FileSystemStorageService(Path rootLocation) { + public FileSystemStorageService(final Path rootLocation) { this.rootLocation = Objects.requireNonNull(rootLocation).toAbsolutePath().normalize(); } @Override - public String store(final String name, final InputStream content) { + public void store(final String key, final InputStream content) { try { if (content == null) throw new IllegalArgumentException("Failed to store empty file."); - Path destinationFile = this.rootLocation.resolve(Paths.get(name)).normalize().toAbsolutePath(); + final Path destinationFile = this.rootLocation.resolve(Paths.get(key)).normalize().toAbsolutePath(); + + if (Files.exists(destinationFile)) + throw StorageKeyAlreadyExistsException.with(key); if (!destinationFile.getParent().equals(this.rootLocation.toAbsolutePath())) throw new IllegalArgumentException("Cannot store file outside current directory."); @@ -35,19 +40,19 @@ public String store(final String name, final InputStream content) { Files.copy(inputStream, destinationFile, StandardCopyOption.REPLACE_EXISTING); } - return destinationFile.toString(); - + } catch (FileAlreadyExistsException e) { + throw StorageKeyAlreadyExistsException.with(key); } catch (IOException e) { throw InternalErrorException.with("Failed to store file.", e); } } @Override - public void delete(final String location) { + public void delete(final String key) { try { - Path filePath = Paths.get(location).normalize().toAbsolutePath(); + final Path filePath = this.rootLocation.resolve(Paths.get(key)).normalize().toAbsolutePath(); if (!filePath.getParent().equals(this.rootLocation.toAbsolutePath())) return; @@ -60,4 +65,18 @@ public void delete(final String location) { } + @Override + public InputStream retrieve(final String key) { + + try { + + final Path filePath = this.rootLocation.resolve(Paths.get(key)).normalize().toAbsolutePath(); + return Files.newInputStream(filePath); + + } catch (IOException e) { + throw InternalErrorException.with("Failed to retrieve file.", e); + } + + } + }