diff --git a/nachrichten-manager-server/pom.xml b/nachrichten-manager-server/pom.xml index 45fe94ccc1ecd26e3d33422d1e86d92603c77c79..28fe26cba3bca3d69eafc29654f923ce7e366430 100644 --- a/nachrichten-manager-server/pom.xml +++ b/nachrichten-manager-server/pom.xml @@ -49,6 +49,7 @@ <vorgang-manager.version>2.17.0-SNAPSHOT</vorgang-manager.version> <muk-postfach.version>0.1.0-SNAPSHOT</muk-postfach.version> <api-lib.version>0.13.0-SNAPSHOT</api-lib.version> + <ozgcloud-common.version>4.5.0-SNAPSHOT</ozgcloud-common.version> </properties> <dependencies> @@ -100,6 +101,12 @@ <version>${api-lib.version}</version> </dependency> + <dependency> + <groupId>de.ozgcloud.common</groupId> + <artifactId>ozgcloud-common-lib</artifactId> + <version>${ozgcloud-common.version}</version> + </dependency> + <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> diff --git a/nachrichten-manager-server/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumGrpcService.java b/nachrichten-manager-server/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumGrpcService.java index 82dcc6d3e12f93a0f9b6e74c1a355e295215b058..3c1918cca9831e57400a8ed37c9b8374956505e7 100644 --- a/nachrichten-manager-server/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumGrpcService.java +++ b/nachrichten-manager-server/src/main/java/de/ozgcloud/nachrichten/antragraum/AntragraumGrpcService.java @@ -35,9 +35,9 @@ import org.springframework.core.task.TaskExecutor; import com.google.protobuf.ByteString; import de.ozgcloud.apilib.file.OzgCloudFile; +import de.ozgcloud.common.binaryfile.GrpcBinaryFileServerDownloader; import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.nachrichten.NachrichtenManagerConfiguration; -import de.ozgcloud.nachrichten.common.grpc.GrpcDownloader; import de.ozgcloud.nachrichten.common.vorgang.VorgangService; import de.ozgcloud.nachrichten.postfach.PostfachNachricht; import io.grpc.stub.CallStreamObserver; @@ -134,9 +134,9 @@ class AntragraumGrpcService extends AntragraumServiceGrpc.AntragraumServiceImplB buildAttachmentDownloader(attachmentFileRequestMapper.fromContentRequest(request), responseObserver).start(); } - GrpcDownloader<GrpcGetAttachmentContentResponse> buildAttachmentDownloader(AttachmentFileRequest request, + GrpcBinaryFileServerDownloader<GrpcGetAttachmentContentResponse> buildAttachmentDownloader(AttachmentFileRequest request, StreamObserver<GrpcGetAttachmentContentResponse> responseObserver) { - return GrpcDownloader.<GrpcGetAttachmentContentResponse>builder() + return GrpcBinaryFileServerDownloader.<GrpcGetAttachmentContentResponse>builder() .callObserver((CallStreamObserver<GrpcGetAttachmentContentResponse>) responseObserver) .taskExecutor(taskExecutor) .downloadConsumer(outputStream -> service.getAttachmentContent(request, outputStream)) diff --git a/nachrichten-manager-server/src/main/java/de/ozgcloud/nachrichten/common/grpc/ExceptionRunnable.java b/nachrichten-manager-server/src/main/java/de/ozgcloud/nachrichten/common/grpc/ExceptionRunnable.java deleted file mode 100644 index 0c22c7faf853d68c011fee0c5e89007ac19f97a8..0000000000000000000000000000000000000000 --- a/nachrichten-manager-server/src/main/java/de/ozgcloud/nachrichten/common/grpc/ExceptionRunnable.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.ozgcloud.nachrichten.common.grpc; - -import java.io.IOException; - -interface ExceptionRunnable { - void run() throws IOException; // NOSONAR -} \ No newline at end of file diff --git a/nachrichten-manager-server/src/main/java/de/ozgcloud/nachrichten/common/grpc/GrpcDownloader.java b/nachrichten-manager-server/src/main/java/de/ozgcloud/nachrichten/common/grpc/GrpcDownloader.java deleted file mode 100644 index 7f34bd46bda9e0334641416c31751bc9b5d13871..0000000000000000000000000000000000000000 --- a/nachrichten-manager-server/src/main/java/de/ozgcloud/nachrichten/common/grpc/GrpcDownloader.java +++ /dev/null @@ -1,108 +0,0 @@ -package de.ozgcloud.nachrichten.common.grpc; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import java.util.function.Function; - -import org.springframework.core.task.TaskExecutor; - -import com.google.protobuf.ByteString; - -import de.ozgcloud.common.errorhandling.TechnicalException; -import io.grpc.stub.CallStreamObserver; -import lombok.Builder; - -public class GrpcDownloader<T> { - - private static final int CHUNK_SIZE = 255 * 1024; - - private CallStreamObserver<T> callObserver; - private Function<ByteString, T> chunkBuilder; - private Consumer<OutputStream> downloadConsumer; - private TaskExecutor taskExecutor; - - private final byte[] buffer = new byte[GrpcDownloader.CHUNK_SIZE]; - private final AtomicBoolean downloadInProgress = new AtomicBoolean(false); - - private PipedInputStream inputStream; - private PipedOutputStream outputStream; - - @Builder - public GrpcDownloader(CallStreamObserver<T> callObserver, Function<ByteString, T> chunkBuilder, - Consumer<OutputStream> downloadConsumer, TaskExecutor taskExecutor) { - this.callObserver = callObserver; - this.chunkBuilder = chunkBuilder; - this.downloadConsumer = downloadConsumer; - this.taskExecutor = taskExecutor; - } - - public void start() { - handleSafety(this::doStart); - } - - void doStart() throws IOException { - setupStreams(); - taskExecutor.execute(this::startDownload); - callObserver.setOnReadyHandler(this::onReadyHandler); - } - - void setupStreams() throws IOException { - outputStream = new PipedOutputStream(); - inputStream = new PipedInputStream(GrpcDownloader.CHUNK_SIZE); - outputStream.connect(inputStream); - } - - void startDownload() { - handleSafety(this::doDownload); - } - - void doDownload() throws IOException { - downloadInProgress.set(true); - downloadConsumer.accept(outputStream); - downloadInProgress.set(false); - outputStream.close(); - } - - synchronized void onReadyHandler() { - if (callObserver.isReady()) { - sendChunks(); - } - } - - void sendChunks() { - handleSafety(this::doSendChunks); - } - - void doSendChunks() throws IOException { - int bytesRead; - while (callObserver.isReady() && (bytesRead = inputStream.read(buffer)) != -1) { - callObserver.onNext(chunkBuilder.apply(ByteString.copyFrom(buffer, 0, bytesRead))); - } - if (!downloadInProgress.get()) { - inputStream.close(); - callObserver.onCompleted(); - } - } - - void handleSafety(ExceptionRunnable runnable) { - try { - runnable.run(); - } catch (IOException e) { - try { - inputStream.close(); - outputStream.close(); - } catch (IOException e1) { - throwException(e1); - } - throwException(e); - } - } - - private void throwException(IOException e) { - throw new TechnicalException("Error occurred during downloading file content download.", e); - } -} \ No newline at end of file diff --git a/nachrichten-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumGrpcServiceTest.java b/nachrichten-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumGrpcServiceTest.java index 8584a2d117dd7a7aa0c47ca6c54910823bacc089..cde69a9d74df8dc17d6fa5aedf4859282c59e717 100644 --- a/nachrichten-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumGrpcServiceTest.java +++ b/nachrichten-manager-server/src/test/java/de/ozgcloud/nachrichten/antragraum/AntragraumGrpcServiceTest.java @@ -52,8 +52,8 @@ import com.thedeanda.lorem.LoremIpsum; import de.ozgcloud.apilib.file.OzgCloudFile; import de.ozgcloud.apilib.file.OzgCloudFileTestFactory; +import de.ozgcloud.common.binaryfile.GrpcBinaryFileServerDownloader; import de.ozgcloud.common.errorhandling.TechnicalException; -import de.ozgcloud.nachrichten.common.grpc.GrpcDownloader; import de.ozgcloud.nachrichten.common.vorgang.GrpcServiceKontoTestFactory; import de.ozgcloud.nachrichten.common.vorgang.Vorgang; import de.ozgcloud.nachrichten.common.vorgang.VorgangService; @@ -473,7 +473,7 @@ class AntragraumGrpcServiceTest { @Mock private StreamObserver<GrpcGetAttachmentContentResponse> responseObserver; @Mock - private GrpcDownloader<GrpcGetAttachmentContentResponse> downloader; + private GrpcBinaryFileServerDownloader<GrpcGetAttachmentContentResponse> downloader; private final GrpcGetAttachmentContentRequest grpcRequest = GrpcGetAttachmentContentRequestTestFactory.create(); private final AttachmentFileRequest request = AttachmentFileRequestTestFactory.create(); @@ -517,8 +517,8 @@ class AntragraumGrpcServiceTest { @Mock private OutputStream outputStream; - private MockedConstruction<GrpcDownloader> downloaderMockedConstruction; - private GrpcDownloader<GrpcGetAttachmentContentResponse> downloader; + private MockedConstruction<GrpcBinaryFileServerDownloader> downloaderMockedConstruction; + private GrpcBinaryFileServerDownloader<GrpcGetAttachmentContentResponse> downloader; private StreamObserver<GrpcGetAttachmentContentResponse> setResponseObserver; private TaskExecutor setTaskExecutor; private Consumer<OutputStream> setDownloadConsumer; @@ -530,7 +530,7 @@ class AntragraumGrpcServiceTest { @SuppressWarnings("unchecked") @BeforeEach void mock() { - downloaderMockedConstruction = mockConstruction(GrpcDownloader.class, (downloader, context) -> { + downloaderMockedConstruction = mockConstruction(GrpcBinaryFileServerDownloader.class, (downloader, context) -> { setResponseObserver = (StreamObserver<GrpcGetAttachmentContentResponse>) context.arguments().get(0); setChunkBuilder = (Function<ByteString, GrpcGetAttachmentContentResponse>) context.arguments().get(1); setDownloadConsumer = (Consumer<OutputStream>) context.arguments().get(2); @@ -585,7 +585,7 @@ class AntragraumGrpcServiceTest { assertThat(returnedDownloader).isEqualTo(downloader); } - private GrpcDownloader<GrpcGetAttachmentContentResponse> callBuildAttachmentDownloader() { + private GrpcBinaryFileServerDownloader<GrpcGetAttachmentContentResponse> callBuildAttachmentDownloader() { return grpcService.buildAttachmentDownloader(request, responseObserver); } } diff --git a/nachrichten-manager-server/src/test/java/de/ozgcloud/nachrichten/common/grpc/GrpcDownloaderTest.java b/nachrichten-manager-server/src/test/java/de/ozgcloud/nachrichten/common/grpc/GrpcDownloaderTest.java deleted file mode 100644 index 4f7c0c5b85abe8af0b8b9a958a8392ea738b2591..0000000000000000000000000000000000000000 --- a/nachrichten-manager-server/src/test/java/de/ozgcloud/nachrichten/common/grpc/GrpcDownloaderTest.java +++ /dev/null @@ -1,330 +0,0 @@ -package de.ozgcloud.nachrichten.common.grpc; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import java.util.function.Function; - -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.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.Spy; -import org.springframework.core.task.TaskExecutor; -import org.springframework.test.util.ReflectionTestUtils; - -import com.google.protobuf.ByteString; - -import de.ozgcloud.common.errorhandling.TechnicalException; -import io.grpc.stub.ClientCallStreamObserver; -import lombok.SneakyThrows; - -class GrpcDownloaderTest { - - @SuppressWarnings("unchecked") - private ClientCallStreamObserver<GrpcResponseDummy> callObserver = Mockito.mock(ClientCallStreamObserver.class); - @SuppressWarnings("unchecked") - private Function<ByteString, GrpcResponseDummy> chunkBuilder = Mockito.mock(Function.class); - @SuppressWarnings("unchecked") - private Consumer<OutputStream> downloadConsumer = Mockito.mock(Consumer.class); - private TaskExecutor taskExecutor = mock(TaskExecutor.class); - - @Spy - private GrpcDownloader<GrpcResponseDummy> downloader = GrpcDownloader.<GrpcResponseDummy>builder() - .callObserver(callObserver) - .downloadConsumer(downloadConsumer) - .chunkBuilder(chunkBuilder) - .taskExecutor(taskExecutor) - .build(); - - @DisplayName("Start") - @Nested - class TestStart { - - @SneakyThrows - @Test - void shouldCallDoStart() { - doNothing().when(downloader).doStart(); - - downloader.start(); - - verify(downloader).doStart(); - } - - @SneakyThrows - @Test - void shouldCallHandleSafety() { - doNothing().when(downloader).doStart(); - - downloader.start(); - - verify(downloader).handleSafety(any(ExceptionRunnable.class)); - } - - @DisplayName("do") - @Nested - class TestDoStart { - - @Captor - private ArgumentCaptor<Runnable> runnableCaptor; - - @SneakyThrows - @Test - void shouldSetupStreams() { - downloader.doStart(); - - verify(downloader).setupStreams(); - } - - @SneakyThrows - @Test - void shouldCallTaskExecutor() { - downloader.doStart(); - - verify(taskExecutor).execute(any()); - } - - @SneakyThrows - @Test - void shouldStartDownload() { - doNothing().when(downloader).startDownload(); - - downloader.doStart(); - - verify(taskExecutor).execute(runnableCaptor.capture()); - runnableCaptor.getValue().run(); - verify(downloader).startDownload(); - } - - @SneakyThrows - @Test - void shouldSetOnReadyHandler() { - downloader.doStart(); - - verify(callObserver).setOnReadyHandler(runnableCaptor.capture()); - assertThat(runnableCaptor.getValue()).isNotNull(); - } - } - } - - @DisplayName("Start download") - @Nested - class TestStartDownload { - - @SneakyThrows - @Test - void shouldCallHandleSafety() { - doNothing().when(downloader).doDownload(); - - downloader.startDownload(); - - verify(downloader).handleSafety(any(ExceptionRunnable.class)); - } - - @SneakyThrows - @Test - void shouldCallDoDownoad() { - doNothing().when(downloader).doDownload(); - - downloader.startDownload(); - - verify(downloader).doDownload(); - } - - @DisplayName("do") - @Nested - class TestDoDownload { - - @Mock - private PipedOutputStream outputStream; - - @BeforeEach - void mock() { - ReflectionTestUtils.setField(downloader, "outputStream", outputStream); - } - - @SneakyThrows - @Test - void shouldCallDownloadConsumer() { - downloader.doDownload(); - - verify(downloadConsumer).accept(outputStream); - } - - @SneakyThrows - @Test - void shouldCloseOutputstream() { - downloader.doDownload(); - - verify(outputStream).close(); - } - } - } - - @DisplayName("On ready handler") - @Nested - class TestOnReadyHandler { - - @Test - void shouldSendChunksIfCallObserverIsReady() { - when(callObserver.isReady()).thenReturn(true); - doNothing().when(downloader).sendChunks(); - - downloader.onReadyHandler(); - - verify(downloader).sendChunks(); - } - } - - @DisplayName("Send chunks") - @Nested - class TestSendChunks { - - @SneakyThrows - @Test - void shouldCallHandleSafety() { - doNothing().when(downloader).doSendChunks(); - - downloader.sendChunks(); - - verify(downloader).handleSafety(any(ExceptionRunnable.class)); - } - - @SneakyThrows - @Test - void shouldCallDoDownoad() { - doNothing().when(downloader).doSendChunks(); - - downloader.sendChunks(); - - verify(downloader).doSendChunks(); - } - - @DisplayName("do") - @Nested - class TestDoSendChunks { - - @Mock - private PipedInputStream inputStream; - - private final int data = 20; - - @BeforeEach - void mock() { - ReflectionTestUtils.setField(downloader, "inputStream", inputStream); - } - - @SneakyThrows - @DisplayName("should send next chunk if callObserver is ready and stream already received data") - @Test - void shouldCallOnNext() { - when(callObserver.isReady()).thenReturn(true); - when(inputStream.read(any())).thenReturn(data, -1); - - downloader.doSendChunks(); - - verify(callObserver).onNext(any()); - } - - @SneakyThrows - @DisplayName("should call complete grpc stream if download has finished and stream has no data left") - @Test - void shouldCallOnCompleted() { - ReflectionTestUtils.setField(downloader, "downloadInProgress", new AtomicBoolean(false)); - - downloader.doSendChunks(); - - verify(callObserver).onCompleted(); - } - - @SneakyThrows - @Test - void shouldCloseInputStream() { - ReflectionTestUtils.setField(downloader, "downloadInProgress", new AtomicBoolean(false)); - - downloader.doSendChunks(); - - verify(inputStream).close(); - } - } - } - - @DisplayName("Handle safety") - @Nested - class TestHandleSafety { - - @DisplayName("on exception") - @Nested - class TestOnException { - - @Mock - private PipedOutputStream outputStream; - @Mock - private PipedInputStream inputStream; - - private final IOException exception = new IOException(); - - @SneakyThrows - @BeforeEach - void mock() { - ReflectionTestUtils.setField(downloader, "inputStream", inputStream); - ReflectionTestUtils.setField(downloader, "outputStream", outputStream); - } - - @SneakyThrows - @Test - void shouldThrowTechnicalException() { - assertThatThrownBy(this::handleSafety) - .isInstanceOf(TechnicalException.class) - .extracting(exception -> exception.getCause()).isEqualTo(exception); - } - - @SneakyThrows - @Test - void shouldCloseOutputStream() { - try { - handleSafety(); - } catch (Exception e) { - // do nothing - } - - verify(outputStream).close(); - } - - @SneakyThrows - @Test - void shouldCloseInputStream() { - try { - handleSafety(); - } catch (Exception e) { - // do nothing - } - - verify(inputStream).close(); - } - - private void handleSafety() { - downloader.handleSafety(this::dummyMethodThrowingException); - } - - private void dummyMethodThrowingException() throws IOException { - throw exception; - } - } - - } - - class GrpcResponseDummy { - } -} \ No newline at end of file