Skip to content
Snippets Groups Projects
Commit 83af24d2 authored by OZGCloud's avatar OZGCloud
Browse files

Merge pull request 'OZG-5121 Fileupload' (#8) from OZG-5121-Fileupload into master

parents 4b26f7d4 39eb1f74
No related branches found
No related tags found
No related merge requests found
package de.ozgcloud.apilib.file;
import java.io.InputStream;
import java.io.OutputStream;
public interface OzgCloudFileService {
......@@ -7,4 +8,6 @@ public interface OzgCloudFileService {
OzgCloudFile getFile(OzgCloudFileId id);
void writeFileDataToStream(OzgCloudFileId id, OutputStream streamToWriteData);
OzgCloudFileId uploadFile(OzgCloudUploadFile file, InputStream dataStream);
}
package de.ozgcloud.apilib.file;
import lombok.Builder;
import lombok.Getter;
@Builder
@Getter
public class OzgCloudUploadFile {
private String fileName;
private String contentType;
private String vorgangId;
private String fieldName;
}
package de.ozgcloud.apilib.file.dummy;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.stereotype.Service;
import de.ozgcloud.apilib.file.OzgCloudFile;
import de.ozgcloud.apilib.file.OzgCloudFileId;
import de.ozgcloud.apilib.file.OzgCloudFileService;
import de.ozgcloud.apilib.file.OzgCloudUploadFile;
import de.ozgcloud.common.errorhandling.TechnicalException;
@Service
......@@ -34,8 +37,14 @@ public class DummyOzgCloudFileService implements OzgCloudFileService {
try {
streamToWriteData.write(TEST_DATA);
} catch (IOException e) {
throw new TechnicalException("Erro wrting dummy data.", e);
throw new TechnicalException("Error writing dummy data.", e);
}
}
@Override
public OzgCloudFileId uploadFile(OzgCloudUploadFile file, InputStream dataStream) {
IOUtils.closeQuietly(dataStream);
return OzgCloudFileId.from("%s-%s".formatted(file.getFileName(), file.getVorgangId()));
}
}
package de.ozgcloud.apilib.file.grpc;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
......@@ -7,23 +8,34 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import org.apache.commons.io.IOUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import com.google.protobuf.ByteString;
import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextAttachingInterceptor;
import de.ozgcloud.apilib.common.callcontext.OzgCloudCallContextProvider;
import de.ozgcloud.apilib.common.errorhandling.NotFoundException;
import de.ozgcloud.apilib.file.OzgCloudFile;
import de.ozgcloud.apilib.file.OzgCloudFileId;
import de.ozgcloud.apilib.file.OzgCloudFileService;
import de.ozgcloud.apilib.file.OzgCloudUploadFile;
import de.ozgcloud.common.binaryfile.FileId;
import de.ozgcloud.common.binaryfile.GrpcFileUploadUtils;
import de.ozgcloud.common.binaryfile.GrpcFileUploadUtils.FileSender;
import de.ozgcloud.common.errorhandling.TechnicalException;
import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceBlockingStub;
import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceStub;
import de.ozgcloud.vorgang.grpc.binaryFile.GrpcBinaryFilesRequest;
import de.ozgcloud.vorgang.grpc.binaryFile.GrpcFindFilesResponse;
import de.ozgcloud.vorgang.grpc.binaryFile.GrpcGetBinaryFileDataRequest;
import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileMetaData;
import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileRequest;
import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileResponse;
import de.ozgcloud.vorgang.grpc.file.GrpcOzgFile;
import io.grpc.stub.CallStreamObserver;
import io.grpc.stub.StreamObserver;
import lombok.RequiredArgsConstructor;
import lombok.extern.java.Log;
import net.devh.boot.grpc.client.inject.GrpcClient;
......@@ -99,7 +111,53 @@ public class GrpcOzgCloudFileService implements OzgCloudFileService {
return blockingStub.withInterceptors(new OzgCloudCallContextAttachingInterceptor(contextProvider));
}
@Override
public OzgCloudFileId uploadFile(OzgCloudUploadFile uploadFile, InputStream dataStream) {
var resultFuture = GrpcFileUploadUtils.createSender(this::buildChunkRequest, dataStream, this::buildCallStreamObserver)
.withMetaData(buildMetaDataRequest(uploadFile))
.send();
var uploadBinaryFileResponse = waitUntilFutureToComplete(resultFuture, dataStream);
return OzgCloudFileId.from(uploadBinaryFileResponse.getFileId());
}
GrpcUploadBinaryFileRequest buildChunkRequest(byte[] bytes, Integer length) {
return GrpcUploadBinaryFileRequest.newBuilder().setFileContent((ByteString.copyFrom(bytes, 0, length))).build();
}
CallStreamObserver<GrpcUploadBinaryFileRequest> buildCallStreamObserver(
StreamObserver<GrpcUploadBinaryFileResponse> responseObserver) {
return (CallStreamObserver<GrpcUploadBinaryFileRequest>) getAsyncServiceStub().uploadBinaryFileAsStream(responseObserver);
}
BinaryFileServiceStub getAsyncServiceStub() {
return asyncServiceStub.withInterceptors(new OzgCloudCallContextAttachingInterceptor(contextProvider));
}
GrpcUploadBinaryFileRequest buildMetaDataRequest(OzgCloudUploadFile uploadFile) {
return GrpcUploadBinaryFileRequest.newBuilder()
.setMetadata(GrpcUploadBinaryFileMetaData.newBuilder()
.setFileName(uploadFile.getFileName())
.setContentType(uploadFile.getContentType())
.setVorgangId(uploadFile.getVorgangId())
.setField(uploadFile.getFieldName())
.build())
.build();
}
GrpcUploadBinaryFileResponse waitUntilFutureToComplete(FileSender<GrpcUploadBinaryFileRequest, GrpcUploadBinaryFileResponse> fileSender,
InputStream fileContentStream) {
try {
return fileSender.getResultFuture().get(10, TimeUnit.MINUTES);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
fileSender.cancelOnError(e);
throw new TechnicalException("Waiting for finishing upload was interrupted.", e);
} catch (ExecutionException | TimeoutException e) {
fileSender.cancelOnTimeout();
throw new TechnicalException("Error / Timeout on uploading data.", e);
} finally {
IOUtils.closeQuietly(fileContentStream);
}
}
}
package de.ozgcloud.apilib.file;
import de.ozgcloud.apilib.file.OzgCloudUploadFile.OzgCloudUploadFileBuilder;
import de.ozgcloud.apilib.vorgang.OzgCloudVorgangTestFactory;
public class OzgCloudUploadFileTestFactory {
public static final String FILE_NAME = "test.txt";
public static final String CONTENT_TYPE = "text/plain";
public static final String FIELD_NAME = "field";
public static OzgCloudUploadFile create() {
return createBuilder().build();
}
private static OzgCloudUploadFileBuilder createBuilder() {
return OzgCloudUploadFile.builder()
.fileName(FILE_NAME)
.contentType(CONTENT_TYPE)
.vorgangId(OzgCloudVorgangTestFactory.ID.toString())
.fieldName(FIELD_NAME);
}
}
package de.ozgcloud.apilib.file.grpc;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.io.InputStream;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mapstruct.factory.Mappers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import de.ozgcloud.apilib.common.errorhandling.NotFoundException;
import de.ozgcloud.apilib.file.OzgCloudFileTestFactory;
import de.ozgcloud.apilib.file.OzgCloudUploadFileTestFactory;
import de.ozgcloud.common.binaryfile.GrpcFileUploadUtils;
import de.ozgcloud.common.binaryfile.GrpcFileUploadUtils.FileSender;
import de.ozgcloud.common.errorhandling.TechnicalException;
import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceBlockingStub;
import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceStub;
import de.ozgcloud.vorgang.grpc.binaryFile.GrpcFindFilesResponse;
import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileMetaData;
import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileRequest;
import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileResponse;
import io.grpc.stub.CallStreamObserver;
import io.grpc.stub.StreamObserver;
import lombok.SneakyThrows;
class GrpcOzgCloudFileServiceTest {
@Spy
@InjectMocks
private GrpcOzgCloudFileService service;
@Mock
private BinaryFileServiceBlockingStub blockingStub;
@Mock
private BinaryFileServiceStub asyncServiceStub;
@Spy
private OzgCloudFileMapper mapper = Mappers.getMapper(OzgCloudFileMapper.class);
......@@ -35,7 +61,8 @@ class GrpcOzgCloudFileServiceTest {
@BeforeEach
void init() {
when(blockingStub.withInterceptors(any())).thenReturn(blockingStub);
when(blockingStub.findBinaryFilesMetaData(any())).thenReturn(GrpcFindFilesResponse.newBuilder().addFile(GrpcOzgFileTestFactory.create()).build());
when(blockingStub.findBinaryFilesMetaData(any())).thenReturn(
GrpcFindFilesResponse.newBuilder().addFile(GrpcOzgFileTestFactory.create()).build());
}
@Test
......@@ -93,4 +120,268 @@ class GrpcOzgCloudFileServiceTest {
}
}
@Nested
class TestUploadFile {
@Nested
class TestUploadFileData {
@Mock
private FileSender<GrpcUploadBinaryFileRequest, GrpcUploadBinaryFileResponse> fileSender;
@Mock
private GrpcUploadBinaryFileRequest uploadBinaryFileRequest;
GrpcUploadBinaryFileResponse response = GrpcUploadBinaryFileResponse.newBuilder().setFileId(OzgCloudFileTestFactory.ID.toString())
.build();
@BeforeEach
void init() {
doReturn(response).when(service).waitUntilFutureToComplete(any(), any());
}
@Test
void shouldCallCreateSender() {
when(fileSender.withMetaData(any())).thenReturn(fileSender);
try (var uploadUtils = Mockito.mockStatic(GrpcFileUploadUtils.class)) {
uploadUtils.when(() -> GrpcFileUploadUtils.createSender(any(), any(), any())).thenReturn(fileSender);
var dataStream = InputStream.nullInputStream();
service.uploadFile(OzgCloudUploadFileTestFactory.create(), dataStream);
uploadUtils.verify(() -> GrpcFileUploadUtils.createSender(any(), eq(dataStream), any()));
}
}
@Test
void shouldCallBuildMetadataRequest() {
when(asyncServiceStub.withInterceptors(any())).thenReturn(asyncServiceStub);
var file = OzgCloudUploadFileTestFactory.create();
service.uploadFile(file, InputStream.nullInputStream());
verify(service).buildMetaDataRequest(file);
}
@Test
void shoudlSetMetadata() {
try (var uploadUtils = Mockito.mockStatic(GrpcFileUploadUtils.class)) {
uploadUtils.when(() -> GrpcFileUploadUtils.createSender(any(), any(), any())).thenReturn(fileSender);
when(fileSender.withMetaData(any())).thenReturn(fileSender);
doReturn(uploadBinaryFileRequest).when(service).buildMetaDataRequest(any());
service.uploadFile(OzgCloudUploadFileTestFactory.create(), InputStream.nullInputStream());
verify(fileSender).withMetaData(uploadBinaryFileRequest);
}
}
@Test
void shouldCallSend() {
try (var uploadUtils = Mockito.mockStatic(GrpcFileUploadUtils.class)) {
uploadUtils.when(() -> GrpcFileUploadUtils.createSender(any(), any(), any())).thenReturn(fileSender);
when(fileSender.withMetaData(any())).thenReturn(fileSender);
service.uploadFile(OzgCloudUploadFileTestFactory.create(), InputStream.nullInputStream());
verify(fileSender).send();
}
}
@Test
void shouldCallWaitUntilFutureToComplete() {
try (var uploadUtils = Mockito.mockStatic(GrpcFileUploadUtils.class)) {
uploadUtils.when(() -> GrpcFileUploadUtils.createSender(any(), any(), any())).thenReturn(fileSender);
when(fileSender.withMetaData(any())).thenReturn(fileSender);
when(fileSender.send()).thenReturn(fileSender);
var dataStream = InputStream.nullInputStream();
service.uploadFile(OzgCloudUploadFileTestFactory.create(), dataStream);
verify(service).waitUntilFutureToComplete(fileSender, dataStream);
}
}
@Test
void shouldReturnResult() {
when(asyncServiceStub.withInterceptors(any())).thenReturn(asyncServiceStub);
var result = service.uploadFile(OzgCloudUploadFileTestFactory.create(), InputStream.nullInputStream());
assertThat(result).isEqualTo(OzgCloudFileTestFactory.ID);
}
private GrpcUploadBinaryFileRequest createRequest() {
return GrpcUploadBinaryFileRequest.newBuilder()
.setMetadata(GrpcUploadBinaryFileMetaDataTestFactory.create())
.build();
}
}
@Nested
class TestBuildChunkResponse {
private static byte[] FILE_CONTENT = "file content".getBytes();
@Test
void shouldSetFileContent() {
var grpcUploadBinaryFileRequest = service.buildChunkRequest(FILE_CONTENT, FILE_CONTENT.length);
assertThat(grpcUploadBinaryFileRequest.getFileContent().toByteArray()).isEqualTo(FILE_CONTENT);
}
}
@Nested
class TestBuildCallStreamObserver {
@Mock
private StreamObserver<GrpcUploadBinaryFileResponse> responseObserver;
@Mock
private CallStreamObserver<GrpcUploadBinaryFileRequest> requestObserver;
@BeforeEach
void init() {
doReturn(asyncServiceStub).when(service).getAsyncServiceStub();
}
@Test
void shouldCallGetAsyncServiceStub() {
service.buildCallStreamObserver(responseObserver);
verify(service).getAsyncServiceStub();
}
@Test
void shouldCallUploadBinaryFileAsStream() {
service.buildCallStreamObserver(responseObserver);
verify(asyncServiceStub).uploadBinaryFileAsStream(responseObserver);
}
@Test
void shouldReturnRequestObserver() {
when(asyncServiceStub.uploadBinaryFileAsStream(any())).thenReturn(requestObserver);
var result = service.buildCallStreamObserver(responseObserver);
assertThat(result).isEqualTo(requestObserver);
}
}
@Nested
class TestBuildMetadataRequest {
@Test
void shouldSetMetadata() {
var result = buildMetadataRequest();
assertThat(result.getMetadata()).isNotEqualTo(GrpcUploadBinaryFileMetaData.getDefaultInstance());
}
@Test
void shouldSetFileName() {
var uploadFile = OzgCloudUploadFileTestFactory.create();
var result = service.buildMetaDataRequest(uploadFile).getMetadata();
assertThat(result.getFileName()).isEqualTo(uploadFile.getFileName());
}
@Test
void shouldSetContentType() {
var uploadFile = OzgCloudUploadFileTestFactory.create();
var result = service.buildMetaDataRequest(uploadFile).getMetadata();
assertThat(result.getContentType()).isEqualTo(uploadFile.getContentType());
}
@Test
void shouldSetVorgangId() {
var uploadFile = OzgCloudUploadFileTestFactory.create();
var result = service.buildMetaDataRequest(uploadFile).getMetadata();
assertThat(result.getVorgangId()).isEqualTo(uploadFile.getVorgangId().toString());
}
@Test
void shouldSetField() {
var uploadFile = OzgCloudUploadFileTestFactory.create();
var result = service.buildMetaDataRequest(uploadFile).getMetadata();
assertThat(result.getField()).isEqualTo(uploadFile.getFieldName());
}
private GrpcUploadBinaryFileRequest buildMetadataRequest() {
return service.buildMetaDataRequest(OzgCloudUploadFileTestFactory.create());
}
}
@Nested
class TestWaitUntilFutureToComplete {
private static final InputStream DATA_STREAM = InputStream.nullInputStream();
@Mock
private FileSender<GrpcUploadBinaryFileRequest, GrpcUploadBinaryFileResponse> fileSender;
@Mock
private CompletableFuture<GrpcUploadBinaryFileResponse> future;
@Test
void shouldWaitUntilFutureToComplete() {
when(fileSender.getResultFuture()).thenReturn(future);
waitUntilFutureToComplete();
verify(fileSender).getResultFuture();
}
@SneakyThrows
@Test
void shouldWaitWithTimeout() {
when(fileSender.getResultFuture()).thenReturn(future);
waitUntilFutureToComplete();
verify(future).get(10, TimeUnit.MINUTES);
}
@Test
void shouldCloseDatastream() {
try(var ioUtils = Mockito.mockStatic(IOUtils.class)) {
when(fileSender.getResultFuture()).thenReturn(future);
waitUntilFutureToComplete();
ioUtils.verify(() -> IOUtils.closeQuietly(DATA_STREAM));
}
}
@Nested
class TestThrowException {
@BeforeEach
void init() {
when(fileSender.getResultFuture()).thenReturn(future);
}
@SneakyThrows
@DisplayName("should throw TechnicalException")
@ParameterizedTest(name = "when {0} is thrown")
@ValueSource(classes = { InterruptedException.class, ExecutionException.class, TimeoutException.class })
void shouldHandleInterruptedException(Class<? extends Exception> exceptionClass) {
when(future.get(anyLong(), any())).thenThrow(exceptionClass);
assertThrows(TechnicalException.class, TestWaitUntilFutureToComplete.this::waitUntilFutureToComplete);
}
}
void waitUntilFutureToComplete() {
service.waitUntilFutureToComplete(fileSender, DATA_STREAM);
}
}
}
}
package de.ozgcloud.apilib.file.grpc;
import de.ozgcloud.apilib.file.OzgCloudUploadFileTestFactory;
import de.ozgcloud.apilib.vorgang.OzgCloudVorgangTestFactory;
import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileMetaData;
public class GrpcUploadBinaryFileMetaDataTestFactory {
public static GrpcUploadBinaryFileMetaData create() {
return createBuilder().build();
}
private static GrpcUploadBinaryFileMetaData.Builder createBuilder() {
return GrpcUploadBinaryFileMetaData.newBuilder()
.setFileName(OzgCloudUploadFileTestFactory.FILE_NAME)
.setContentType(OzgCloudUploadFileTestFactory.CONTENT_TYPE)
.setVorgangId(OzgCloudVorgangTestFactory.ID.toString())
.setField(OzgCloudUploadFileTestFactory.FIELD_NAME);
}
}
......@@ -28,7 +28,7 @@
<failsafe-plugin.version>3.2.2</failsafe-plugin.version>
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version>
<vorgang-manager.version>2.0.0</vorgang-manager.version>
<vorgang-manager.version>2.5.0-SNAPSHOT</vorgang-manager.version>
<user-manager.version>2.1.0</user-manager.version>
</properties>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment