diff --git a/document-manager-server/src/main/java/de/ozgcloud/document/DocumentManagerConfiguration.java b/document-manager-server/src/main/java/de/ozgcloud/document/DocumentManagerConfiguration.java index fdc2559a258b731f8c9a1052761da1f6c681a933..3dfcad69044e784b0cb7accb5e8f19c8125d95ec 100644 --- a/document-manager-server/src/main/java/de/ozgcloud/document/DocumentManagerConfiguration.java +++ b/document-manager-server/src/main/java/de/ozgcloud/document/DocumentManagerConfiguration.java @@ -40,11 +40,19 @@ import net.devh.boot.grpc.client.inject.GrpcClient; @Configuration public class DocumentManagerConfiguration { - private static final String GRPC_USER_MANAGER_NAME = "user-manager"; - private static final String GRPC_COMMAND_MANAGER_NAME = "command-manager"; + public static final String GRPC_USER_MANAGER_NAME = "user-manager"; + public static final String GRPC_COMMAND_MANAGER_NAME = "command-manager"; + public static final String GRPC_VORGANG_MANAGER_NAME = "vorgang-manager"; public static final String COMMAND_SERVICE_NAME = "document_OzgCloudCommandService"; public static final String USER_PROFILE_SERVICE_NAME = "document_OzgCloudUserProfileService"; + public static final String CALL_CONTEXT_CLIENT_INTERCEPTOR_NAME = "document_CallContextClientInterceptor"; + public static final String VORGANG_SERVICE_NAME = "document_VorgangService"; + public static final String VORGANG_REMOTE_SERVICE_NAME = "document_VorgangRemoteService"; + public static final String VORGANG_ATTACHED_ITEM_SERVICE_NAME = "document_VorgangAttachedItemService"; + public static final String VORGANG_ATTACHED_ITEM_REMOTE_SERVICE_NAME = "document_VorgangAttachedItemRemoteService"; + public static final String CLIENT_ATTRIBUTE_SERVICE_NAME = "document_ClientAttributeService"; + public static final String CLIENT_ATTRIBUTE_REMOTE_SERVICE_NAME = "document_ClientAttributeRemoteService"; @GrpcClient(GRPC_COMMAND_MANAGER_NAME) private CommandServiceGrpc.CommandServiceBlockingStub commandServiceStub; diff --git a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/BescheidCallContextAttachingInterceptor.java b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/BescheidCallContextAttachingInterceptor.java index 6837a33f67fca31cd204c6b6ed8e4dd7f7f2c734..30c55554d299185cd1c66971659ae2a7282109c5 100644 --- a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/BescheidCallContextAttachingInterceptor.java +++ b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/BescheidCallContextAttachingInterceptor.java @@ -7,6 +7,7 @@ import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import de.ozgcloud.document.DocumentManagerConfiguration; import de.ozgcloud.document.bescheid.common.callcontext.CurrentUserService; import io.grpc.CallOptions; import io.grpc.Channel; @@ -16,7 +17,7 @@ import io.grpc.ForwardingClientCall.SimpleForwardingClientCall; import io.grpc.Metadata; import io.grpc.MethodDescriptor; -@Component("bescheidCallContextInterceptor") +@Component(DocumentManagerConfiguration.CALL_CONTEXT_CLIENT_INTERCEPTOR_NAME) public class BescheidCallContextAttachingInterceptor implements ClientInterceptor { public static final String BESCHEID_MANAGER_CLIENT_NAME = "OzgCloud_BescheidManager"; diff --git a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/BescheidEventListener.java b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/BescheidEventListener.java index 13829cac80c313d668d4c384b084354bc84181ee..ed636e15736c6cf74206f92c37de9f1d57ec6440 100644 --- a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/BescheidEventListener.java +++ b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/BescheidEventListener.java @@ -34,11 +34,13 @@ import java.util.stream.Collectors; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.event.EventListener; import org.springframework.security.core.context.SecurityContext; import org.springframework.stereotype.Component; +import de.ozgcloud.document.DocumentManagerConfiguration; import de.ozgcloud.document.bescheid.Bescheid.SendBy; import de.ozgcloud.document.bescheid.binaryfile.BinaryFileService; import de.ozgcloud.document.bescheid.common.callcontext.CurrentUserService; @@ -93,6 +95,7 @@ class BescheidEventListener { private final BescheidService service; private final BinaryFileService fileService; + @Qualifier(DocumentManagerConfiguration.VORGANG_ATTACHED_ITEM_SERVICE_NAME) private final AttachedItemService attachedItemService; private final DocumentService documentService; diff --git a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/BescheidService.java b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/BescheidService.java index a942c708721ff27ae8ccc2b6d9d317203520db02..96da3677297df7960bdcee5ad1271417b186bec1 100644 --- a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/BescheidService.java +++ b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/BescheidService.java @@ -66,7 +66,9 @@ public class BescheidService { static final String FIELD_ATTACHMENTS = "attachments"; static final String SUBJECT = "Ihr Bescheid zum Antrag"; + @Qualifier(DocumentManagerConfiguration.VORGANG_SERVICE_NAME) private final VorgangService vorgangService; + @Qualifier(DocumentManagerConfiguration.VORGANG_ATTACHED_ITEM_SERVICE_NAME) private final AttachedItemService attachedItemService; private final UserProfileService userProfileService; @Qualifier(DocumentManagerConfiguration.COMMAND_SERVICE_NAME) @@ -74,6 +76,7 @@ public class BescheidService { private final AdministrationService administrationService; private final CommandMapper commandMapper; + @Qualifier(DocumentManagerConfiguration.CLIENT_ATTRIBUTE_SERVICE_NAME) private final ClientAttributeService bescheidClientAttributeService; private final BuildProperties buildProperties; private final Optional<BescheidRemoteService> remoteService; diff --git a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/attributes/ClientAttributeRemoteService.java b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/attributes/ClientAttributeRemoteService.java index 311480536d4d7a4b8771bfa37782e39d133f01b3..a3aa3fcf3a2e5601e21a4373d2b4cc21b1ac8fc0 100644 --- a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/attributes/ClientAttributeRemoteService.java +++ b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/attributes/ClientAttributeRemoteService.java @@ -23,9 +23,10 @@ */ package de.ozgcloud.document.bescheid.attributes; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import de.ozgcloud.document.DocumentManagerConfiguration; import de.ozgcloud.document.bescheid.BescheidCallContextAttachingInterceptor; import de.ozgcloud.vorgang.grpc.clientAttribute.ClientAttributeServiceGrpc.ClientAttributeServiceBlockingStub; import de.ozgcloud.vorgang.grpc.clientAttribute.GrpcAccessPermission; @@ -33,16 +34,18 @@ import de.ozgcloud.vorgang.grpc.clientAttribute.GrpcClientAttribute; import de.ozgcloud.vorgang.grpc.clientAttribute.GrpcClientAttributeValue; import de.ozgcloud.vorgang.grpc.clientAttribute.GrpcSetClientAttributeRequest; import io.grpc.ClientInterceptor; +import lombok.RequiredArgsConstructor; import net.devh.boot.grpc.client.inject.GrpcClient; -@Service("bescheid_ClientAttributeRemoteService") +@Service(DocumentManagerConfiguration.CLIENT_ATTRIBUTE_REMOTE_SERVICE_NAME) +@RequiredArgsConstructor class ClientAttributeRemoteService { @GrpcClient("vorgang-manager") - private ClientAttributeServiceBlockingStub serviceBlockingStub; + private final ClientAttributeServiceBlockingStub serviceBlockingStub; - @Autowired - private ClientInterceptor bescheidCallContextInterceptor; + @Qualifier(DocumentManagerConfiguration.CALL_CONTEXT_CLIENT_INTERCEPTOR_NAME) + private final ClientInterceptor bescheidCallContextInterceptor; public void setBooleanReadOnlyClientAttribute(String vorgangId, String attributeName, boolean value) { serviceBlockingStub.withInterceptors(bescheidCallContextInterceptor).set(buildRequest(vorgangId, attributeName, value)); diff --git a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/attributes/ClientAttributeService.java b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/attributes/ClientAttributeService.java index 5f6ffecd4356c211e085657f9bbf13ed431898da..d09c1bb6d4550bae87add4d922d023477946683b 100644 --- a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/attributes/ClientAttributeService.java +++ b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/attributes/ClientAttributeService.java @@ -23,16 +23,19 @@ */ package de.ozgcloud.document.bescheid.attributes; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import de.ozgcloud.document.DocumentManagerConfiguration; import lombok.RequiredArgsConstructor; -@Service("bescheid_ClientAttributeService") +@Service(DocumentManagerConfiguration.CLIENT_ATTRIBUTE_SERVICE_NAME) @RequiredArgsConstructor public class ClientAttributeService { public static final String ATTRIBUTE_NAME_ANTRAG_BEWILLIGT = "antragBewilligt"; + @Qualifier(DocumentManagerConfiguration.CLIENT_ATTRIBUTE_REMOTE_SERVICE_NAME) private final ClientAttributeRemoteService bescheidClientAttributeRemoteService; public void setAntragResult(String vorgangId, boolean antragResult) { diff --git a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/binaryfile/BinaryFileRemoteService.java b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/binaryfile/BinaryFileRemoteService.java index 8af3827ee299c2a2cde22363e8df93311dcaf63e..12c75d382c407e5445fdad47149ea64354ae438f 100644 --- a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/binaryfile/BinaryFileRemoteService.java +++ b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/binaryfile/BinaryFileRemoteService.java @@ -10,38 +10,43 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import com.google.protobuf.ByteString; -import de.ozgcloud.document.bescheid.BescheidResponse; 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.document.DocumentManagerConfiguration; +import de.ozgcloud.document.bescheid.BescheidCallContextAttachingInterceptor; +import de.ozgcloud.document.bescheid.BescheidResponse; import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceStub; 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.command.GrpcCallContext; import io.grpc.stub.CallStreamObserver; import io.grpc.stub.StreamObserver; import lombok.NonNull; +import lombok.RequiredArgsConstructor; import net.devh.boot.grpc.client.inject.GrpcClient; @Service +@RequiredArgsConstructor class BinaryFileRemoteService { - private static final String CALL_CONTEXT_CLIENT = "bescheid-manager"; - private static final String VORGANG_ATTACHMENT_FIELD = "bescheid"; + static final String VORGANG_ATTACHMENT_FIELD = "bescheid"; - @GrpcClient("vorgang-manager") - private BinaryFileServiceStub binaryFileRemoteStub; + @GrpcClient(DocumentManagerConfiguration.GRPC_VORGANG_MANAGER_NAME) + private final BinaryFileServiceStub binaryFileRemoteStub; + @Qualifier(DocumentManagerConfiguration.CALL_CONTEXT_CLIENT_INTERCEPTOR_NAME) + private final BescheidCallContextAttachingInterceptor callContextInterceptor; public FileId uploadBescheidFile(@NonNull BescheidResponse bescheid) { try (var in = openFile(bescheid.getBescheidFile())) { var resultFuture = GrpcFileUploadUtils.createSender(this::buildChunkRequest, in, - this::buildCallStreamObserver) + this::buildCallStreamObserver) .withMetaData(buildMetaDataRequest(bescheid)) .send(); @@ -51,7 +56,7 @@ class BinaryFileRemoteService { } } - private InputStream openFile(File file) { + InputStream openFile(File file) { try { return new FileInputStream(file); } catch (FileNotFoundException e) { @@ -59,11 +64,9 @@ class BinaryFileRemoteService { } } - private GrpcUploadBinaryFileRequest buildMetaDataRequest(BescheidResponse bescheid) { + GrpcUploadBinaryFileRequest buildMetaDataRequest(BescheidResponse bescheid) { return GrpcUploadBinaryFileRequest.newBuilder() .setMetadata(GrpcUploadBinaryFileMetaData.newBuilder() - // TODO remove context - check why needed! - .setContext(GrpcCallContext.newBuilder().setClient(CALL_CONTEXT_CLIENT).build()) .setVorgangId(bescheid.getVorgangId().toString()) .setField(VORGANG_ATTACHMENT_FIELD) .setContentType(bescheid.getContentType()) @@ -73,13 +76,14 @@ class BinaryFileRemoteService { .build(); } - private GrpcUploadBinaryFileRequest buildChunkRequest(byte[] bytes, Integer length) { + GrpcUploadBinaryFileRequest buildChunkRequest(byte[] bytes, Integer length) { return GrpcUploadBinaryFileRequest.newBuilder().setFileContent((ByteString.copyFrom(bytes, 0, length))).build(); } - private CallStreamObserver<GrpcUploadBinaryFileRequest> buildCallStreamObserver( + CallStreamObserver<GrpcUploadBinaryFileRequest> buildCallStreamObserver( StreamObserver<GrpcUploadBinaryFileResponse> responseObserver) { - return (CallStreamObserver<GrpcUploadBinaryFileRequest>) binaryFileRemoteStub.uploadBinaryFileAsStream(responseObserver); + return (CallStreamObserver<GrpcUploadBinaryFileRequest>) binaryFileRemoteStub.withInterceptors(callContextInterceptor) + .uploadBinaryFileAsStream(responseObserver); } GrpcUploadBinaryFileResponse waitUntilFutureToComplete(FileSender<GrpcUploadBinaryFileRequest, GrpcUploadBinaryFileResponse> fileSender, diff --git a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/vorgang/VorgangRemoteService.java b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/vorgang/VorgangRemoteService.java index fdf8940f08e2ba8c84e4fc01a32cb694098fb2f0..95954e0a711717964c4c4d81d0e454549c9cf16c 100644 --- a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/vorgang/VorgangRemoteService.java +++ b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/vorgang/VorgangRemoteService.java @@ -1,24 +1,25 @@ package de.ozgcloud.document.bescheid.vorgang; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import de.ozgcloud.document.DocumentManagerConfiguration; import de.ozgcloud.vorgang.vorgang.GrpcFindVorgangWithEingangRequest; import de.ozgcloud.vorgang.vorgang.VorgangServiceGrpc.VorgangServiceBlockingStub; import io.grpc.ClientInterceptor; import lombok.NonNull; +import lombok.RequiredArgsConstructor; import net.devh.boot.grpc.client.inject.GrpcClient; -@Service("bescheid_VorgangRemoteService") +@Service(DocumentManagerConfiguration.VORGANG_REMOTE_SERVICE_NAME) +@RequiredArgsConstructor class VorgangRemoteService { @GrpcClient("vorgang-manager") - private VorgangServiceBlockingStub vorgangServiceStub; - @Autowired - private BescheidVorgangMapper mapper; - - @Autowired - private ClientInterceptor bescheidCallContextInterceptor; + private final VorgangServiceBlockingStub vorgangServiceStub; + private final BescheidVorgangMapper mapper; + @Qualifier(DocumentManagerConfiguration.CALL_CONTEXT_CLIENT_INTERCEPTOR_NAME) + private final ClientInterceptor bescheidCallContextInterceptor; public Vorgang getById(@NonNull VorgangId vorgangId) { var request = GrpcFindVorgangWithEingangRequest.newBuilder().setId(vorgangId.toString()).build(); diff --git a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/vorgang/VorgangService.java b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/vorgang/VorgangService.java index a5d8c451351889a0a3cc7ffd76f491b9a53fff77..64bc78d7d070cce40b7ee0c73561d581cab436f6 100644 --- a/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/vorgang/VorgangService.java +++ b/document-manager-server/src/main/java/de/ozgcloud/document/bescheid/vorgang/VorgangService.java @@ -1,14 +1,17 @@ package de.ozgcloud.document.bescheid.vorgang; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import de.ozgcloud.document.DocumentManagerConfiguration; import lombok.NonNull; import lombok.RequiredArgsConstructor; -@Service("bescheid_VorgangService") +@Service(DocumentManagerConfiguration.VORGANG_SERVICE_NAME) @RequiredArgsConstructor public class VorgangService { + @Qualifier(DocumentManagerConfiguration.VORGANG_REMOTE_SERVICE_NAME) private final VorgangRemoteService remoteService; public Vorgang getById(@NonNull VorgangId id) { diff --git a/document-manager-server/src/main/java/de/ozgcloud/document/common/attached_item/AttachedItemService.java b/document-manager-server/src/main/java/de/ozgcloud/document/common/attached_item/AttachedItemService.java index db06b2a30ebd55fc14f183891ec8f2a7c895a8f7..9e1b1a66bcaaae247e68c84e7163ddc9d8999b7a 100644 --- a/document-manager-server/src/main/java/de/ozgcloud/document/common/attached_item/AttachedItemService.java +++ b/document-manager-server/src/main/java/de/ozgcloud/document/common/attached_item/AttachedItemService.java @@ -48,7 +48,7 @@ import de.ozgcloud.document.bescheid.BescheidMapper; import de.ozgcloud.document.bescheid.vorgang.VorgangId; import lombok.RequiredArgsConstructor; -@Service +@Service(DocumentManagerConfiguration.VORGANG_ATTACHED_ITEM_SERVICE_NAME) @RequiredArgsConstructor public class AttachedItemService { @@ -60,6 +60,7 @@ public class AttachedItemService { @Qualifier(DocumentManagerConfiguration.COMMAND_SERVICE_NAME) private final OzgCloudCommandService commandService; + @Qualifier(DocumentManagerConfiguration.VORGANG_ATTACHED_ITEM_REMOTE_SERVICE_NAME) private final VorgangAttachedItemRemoteService remoteService; private final CommandMapper commandMapper; diff --git a/document-manager-server/src/main/java/de/ozgcloud/document/common/attached_item/VorgangAttachedItemRemoteService.java b/document-manager-server/src/main/java/de/ozgcloud/document/common/attached_item/VorgangAttachedItemRemoteService.java index 20a6ab4f684d4f060f4f5cb26a76e23ff37ff649..fa32c2c6e946f73d7543735fb2b41cd566ac8aac 100644 --- a/document-manager-server/src/main/java/de/ozgcloud/document/common/attached_item/VorgangAttachedItemRemoteService.java +++ b/document-manager-server/src/main/java/de/ozgcloud/document/common/attached_item/VorgangAttachedItemRemoteService.java @@ -32,9 +32,10 @@ import java.util.Optional; import java.util.stream.Stream; import org.apache.commons.collections.MapUtils; -import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import de.ozgcloud.document.DocumentManagerConfiguration; import de.ozgcloud.document.bescheid.Bescheid; import de.ozgcloud.document.bescheid.BescheidCallContextAttachingInterceptor; import de.ozgcloud.document.bescheid.vorgang.VorgangId; @@ -42,11 +43,13 @@ import de.ozgcloud.vorgang.vorgangAttachedItem.GrpcFindVorgangAttachedItemReques import de.ozgcloud.vorgang.vorgangAttachedItem.GrpcVorgangAttachedItemRequest; import de.ozgcloud.vorgang.vorgangAttachedItem.VorgangAttachedItemServiceGrpc.VorgangAttachedItemServiceBlockingStub; import io.grpc.ClientInterceptor; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import net.devh.boot.grpc.client.inject.GrpcClient; -@Service +@Service(DocumentManagerConfiguration.VORGANG_ATTACHED_ITEM_REMOTE_SERVICE_NAME) @Log4j2 +@RequiredArgsConstructor class VorgangAttachedItemRemoteService { private static final Comparator<AttachedItem> BY_BESCHIEDEN_AM_DESC = (bescheid1, bescheid2) -> { @@ -56,11 +59,10 @@ class VorgangAttachedItemRemoteService { }; @GrpcClient("vorgang-manager") - private VorgangAttachedItemServiceBlockingStub serviceStub; - @Autowired - private ClientInterceptor bescheidCallContextInterceptor; - @Autowired - private AttachedItemMapper attachedItemMapper; + private final VorgangAttachedItemServiceBlockingStub serviceStub; + @Qualifier(DocumentManagerConfiguration.CALL_CONTEXT_CLIENT_INTERCEPTOR_NAME) + private final ClientInterceptor bescheidCallContextInterceptor; + private final AttachedItemMapper attachedItemMapper; public Optional<AttachedItem> findBescheidDraft(VorgangId vorgangId) { return findBescheidDraft(buildFindRequest(vorgangId.toString())); diff --git a/document-manager-server/src/test/java/de/ozgcloud/document/bescheid/binaryfile/BinaryFileRemoteServiceTest.java b/document-manager-server/src/test/java/de/ozgcloud/document/bescheid/binaryfile/BinaryFileRemoteServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b1d5b0cf4bf9f9a92d8b451a9640baaec3131732 --- /dev/null +++ b/document-manager-server/src/test/java/de/ozgcloud/document/bescheid/binaryfile/BinaryFileRemoteServiceTest.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.document.bescheid.binaryfile; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.io.InputStream; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.AfterEach; +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.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Spy; + +import de.ozgcloud.common.binaryfile.FileId; +import de.ozgcloud.common.binaryfile.GrpcFileUploadUtils; +import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.document.bescheid.BescheidCallContextAttachingInterceptor; +import de.ozgcloud.document.bescheid.BescheidResponse; +import de.ozgcloud.document.bescheid.BescheidResponseTestFactory; +import de.ozgcloud.vorgang.grpc.binaryFile.BinaryFileServiceGrpc.BinaryFileServiceStub; +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 BinaryFileRemoteServiceTest { + + @Spy + @InjectMocks + private BinaryFileRemoteService remoteService; + + @Mock + private BinaryFileServiceStub binaryFileServiceStub; + @Mock + private BescheidCallContextAttachingInterceptor callContextInterceptor; + @Mock + private StreamObserver<GrpcUploadBinaryFileResponse> responseObserver; + @Mock + private GrpcUploadBinaryFileResponse grpcUploadResponse; + @Mock + private GrpcFileUploadUtils.FileSender<GrpcUploadBinaryFileRequest, GrpcUploadBinaryFileResponse> initialisedFileSender; + @Mock + private InputStream inputStream; + + @Nested + class TestUploadBescheidFile { + + private static final BescheidResponse BESCHEID_RESPONSE = BescheidResponseTestFactory.create(); + private static final FileId FILE_ID = FileId.createNew(); + + @Mock + private GrpcFileUploadUtils.FileSender<GrpcUploadBinaryFileRequest, GrpcUploadBinaryFileResponse> createdFileSender; + @Mock + private GrpcFileUploadUtils.FileSender<GrpcUploadBinaryFileRequest, GrpcUploadBinaryFileResponse> fileSenderWithMetadata; + @Mock + private CallStreamObserver<GrpcUploadBinaryFileRequest> requestObserver; + @Mock + private GrpcUploadBinaryFileRequest grpcUploadRequest; + + @Captor + private ArgumentCaptor<BiFunction<byte[], Integer, GrpcUploadBinaryFileRequest>> buildChunkRequestCaptor; + @Captor + private ArgumentCaptor<Function<StreamObserver<GrpcUploadBinaryFileResponse>, CallStreamObserver<GrpcUploadBinaryFileRequest>>> buildCallStreamObserverCaptor; + + private MockedStatic<GrpcFileUploadUtils> uploadUtilsMock; + + @BeforeEach + void init() { + doReturn(inputStream).when(remoteService).openFile(any()); + doReturn(grpcUploadResponse).when(remoteService).waitUntilFutureToComplete(any(), any()); + when(grpcUploadResponse.getFileId()).thenReturn(FILE_ID.toString()); + + uploadUtilsMock = mockStatic(GrpcFileUploadUtils.class); + when(createdFileSender.withMetaData(any())).thenReturn(fileSenderWithMetadata); + when(fileSenderWithMetadata.send()).thenReturn(initialisedFileSender); + uploadUtilsMock.when(() -> GrpcFileUploadUtils.createSender(any(), any(), any())).thenReturn(createdFileSender); + } + + @AfterEach + void cleanup() { + uploadUtilsMock.close(); + } + + @Test + void shouldCallOpenFile() { + uploadBescheidFile(); + + verify(remoteService).openFile(BescheidResponseTestFactory.BESCHEID_FILE); + } + + @Test + void shouldCallCreateSender() { + uploadBescheidFile(); + + uploadUtilsMock.verify(() -> GrpcFileUploadUtils.createSender(buildChunkRequestCaptor.capture(), eq(inputStream), + buildCallStreamObserverCaptor.capture())); + verifyCallBuildChunkRequest(); + verifyCallBuildCallStreamObserver(); + } + + @Test + void shouldCallBuildMetaDataRequest() { + uploadBescheidFile(); + + verify(remoteService).buildMetaDataRequest(BESCHEID_RESPONSE); + } + + @Test + void shouldCallWithMetaData() { + doReturn(grpcUploadRequest).when(remoteService).buildMetaDataRequest(any()); + + uploadBescheidFile(); + + verify(createdFileSender).withMetaData(grpcUploadRequest); + } + + @Test + void shouldCallSend() { + uploadBescheidFile(); + + verify(fileSenderWithMetadata).send(); + } + + @Test + void shouldCallWaitUntilFutureToComplete() { + uploadBescheidFile(); + + verify(remoteService).waitUntilFutureToComplete(initialisedFileSender, inputStream); + } + + @Test + void shouldReturnFileId() { + var result = uploadBescheidFile(); + + assertThat(result).isEqualTo(FILE_ID); + } + + private FileId uploadBescheidFile() { + return remoteService.uploadBescheidFile(BESCHEID_RESPONSE); + } + + private void verifyCallBuildChunkRequest() { + doReturn(grpcUploadRequest).when(remoteService).buildChunkRequest(any(), any()); + var bytes = new byte[1]; + var length = 1; + + buildChunkRequestCaptor.getValue().apply(bytes, length); + verify(remoteService).buildChunkRequest(bytes, length); + } + + private void verifyCallBuildCallStreamObserver() { + doReturn(requestObserver).when(remoteService).buildCallStreamObserver(any()); + + buildCallStreamObserverCaptor.getValue().apply(responseObserver); + verify(remoteService).buildCallStreamObserver(responseObserver); + } + } + + @Nested + class TestOpenFile { + + @Test + void shouldReturnInputStream() { + var result = remoteService.openFile(BescheidResponseTestFactory.BESCHEID_FILE); + + assertThat(result).hasBinaryContent(BescheidResponseTestFactory.BESCHEID_FILE_NAME.getBytes()); + } + } + + @Nested + class TestBuildMetaDataRequest { + + @Test + void shouldReturnGrpcUploadBinaryFileRequest() { + var requestMetadata = GrpcUploadBinaryFileMetaDataTestFactory.createBuilder().setField(BinaryFileRemoteService.VORGANG_ATTACHMENT_FIELD) + .build(); + + var result = remoteService.buildMetaDataRequest(BescheidResponseTestFactory.create()); + + assertThat(result.getMetadata()).usingRecursiveComparison().isEqualTo(requestMetadata); + assertThat(result.getFileContent()).isEmpty(); + } + } + + @Nested + class TestBuildChunkRequest { + + @Test + void shouldSetFileContent() { + var bytes = BescheidResponseTestFactory.BESCHEID_FILE_NAME.getBytes(); + var length = BescheidResponseTestFactory.BESCHEID_FILE_NAME.length(); + + var result = remoteService.buildChunkRequest(bytes, length); + + assertThat(result.getFileContent().toStringUtf8()).isEqualTo(BescheidResponseTestFactory.BESCHEID_FILE_NAME); + } + } + + @Nested + class TestBuildCallStreamObserver { + + @Mock + private BinaryFileServiceStub binaryFileServiceStubWithInterceptor; + + @BeforeEach + void init() { + when(binaryFileServiceStub.withInterceptors(callContextInterceptor)).thenReturn(binaryFileServiceStubWithInterceptor); + } + + @Test + void shouldSetInterceptor() { + buildCallStreamObserver(); + + verify(binaryFileServiceStub).withInterceptors(callContextInterceptor); + } + + @Test + void shouldCallUploadBinaryFileAsStream() { + buildCallStreamObserver(); + + verify(binaryFileServiceStubWithInterceptor).uploadBinaryFileAsStream(responseObserver); + } + + private CallStreamObserver<GrpcUploadBinaryFileRequest> buildCallStreamObserver() { + return remoteService.buildCallStreamObserver(responseObserver); + } + } + + @Nested + class TestWaitUntilFutureToComplete { + + @Mock + private GrpcUploadBinaryFileResponse grpcUploadResponse; + @Mock + private CompletableFuture<GrpcUploadBinaryFileResponse> responseFuture; + + @BeforeEach + void init() { + when(initialisedFileSender.getResultFuture()).thenReturn(responseFuture); + } + + @Nested + class TestOnSuccess { + + @SneakyThrows + @BeforeEach + void init() { + when(responseFuture.get(anyLong(), any())).thenReturn(grpcUploadResponse); + } + + @Test + void shouldReturnResponse() { + var result = waitUntilFutureToComplete(); + + assertThat(result).isSameAs(grpcUploadResponse); + } + + @Test + void shouldCloseStream() { + try (var ioUtilsMock = mockStatic(IOUtils.class)) { + waitUntilFutureToComplete(); + + ioUtilsMock.verify(() -> IOUtils.closeQuietly(inputStream)); + } + } + } + + @Nested + class TestOnInterruptedException { + + @Mock + private InterruptedException exception; + + @BeforeEach + void init() throws ExecutionException, InterruptedException, TimeoutException { + when(responseFuture.get(anyLong(), any())).thenThrow(exception); + } + + @Test + void shouldCallCancelOnError() { + assertThrows(TechnicalException.class, TestWaitUntilFutureToComplete.this::waitUntilFutureToComplete); + + verify(initialisedFileSender).cancelOnError(exception); + } + + @Test + void shouldCloseStream() { + try (var ioUtilsMock = mockStatic(IOUtils.class)) { + assertThrows(TechnicalException.class, TestWaitUntilFutureToComplete.this::waitUntilFutureToComplete); + + ioUtilsMock.verify(() -> IOUtils.closeQuietly(inputStream)); + } + } + } + + @Nested + class TestOnException { + + @SneakyThrows + @DisplayName("should cancel on timeout") + @ParameterizedTest(name = "when {0} is thrown") + @ValueSource(classes = { ExecutionException.class, TimeoutException.class }) + void shouldCancelOnTimeoutOnTimeoutException(Class<Exception> exceptionClass) { + when(responseFuture.get(anyLong(), any())).thenThrow(exceptionClass); + + assertThrows(TechnicalException.class, TestWaitUntilFutureToComplete.this::waitUntilFutureToComplete); + + verify(initialisedFileSender).cancelOnTimeout(); + } + + @SneakyThrows + @DisplayName("should close stream") + @ParameterizedTest(name = "when {0} is thrown") + @ValueSource(classes = { ExecutionException.class, TimeoutException.class }) + void shouldCloseStream(Class<Exception> exceptionClass) { + try (var ioUtilsMock = mockStatic(IOUtils.class)) { + when(responseFuture.get(anyLong(), any())).thenThrow(exceptionClass); + + assertThrows(TechnicalException.class, TestWaitUntilFutureToComplete.this::waitUntilFutureToComplete); + + ioUtilsMock.verify(() -> IOUtils.closeQuietly(inputStream)); + } + } + } + + private GrpcUploadBinaryFileResponse waitUntilFutureToComplete() { + return remoteService.waitUntilFutureToComplete(initialisedFileSender, inputStream); + } + } + +} \ No newline at end of file diff --git a/document-manager-server/src/test/java/de/ozgcloud/document/bescheid/binaryfile/GrpcUploadBinaryFileMetaDataTestFactory.java b/document-manager-server/src/test/java/de/ozgcloud/document/bescheid/binaryfile/GrpcUploadBinaryFileMetaDataTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..9680d544810c33865349dad68392fe665d2bd7ed --- /dev/null +++ b/document-manager-server/src/test/java/de/ozgcloud/document/bescheid/binaryfile/GrpcUploadBinaryFileMetaDataTestFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.document.bescheid.binaryfile; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.document.bescheid.BescheidResponseTestFactory; +import de.ozgcloud.document.bescheid.vorgang.VorgangTestFactory; +import de.ozgcloud.vorgang.grpc.binaryFile.GrpcUploadBinaryFileMetaData; + +public class GrpcUploadBinaryFileMetaDataTestFactory { + + public static final String VORGANG_ID = VorgangTestFactory.ID_STR; + public static final String FIELD = LoremIpsum.getInstance().getWords(1); + public static final String CONTENT_TYPE = BescheidResponseTestFactory.CONTENT_TYPE; + public static final int SIZE = BescheidResponseTestFactory.BESCHEID_FILE_SIZE; + public static final String FILE_NAME = BescheidResponseTestFactory.BESCHEID_FILE_NAME; + + public static GrpcUploadBinaryFileMetaData create() { + return createBuilder().build(); + } + + public static GrpcUploadBinaryFileMetaData.Builder createBuilder() { + return GrpcUploadBinaryFileMetaData.newBuilder() + .setVorgangId(VORGANG_ID) + .setField(FIELD) + .setContentType(CONTENT_TYPE) + .setSize(SIZE) + .setFileName(FILE_NAME); + } +} diff --git a/lombok.config b/lombok.config index a06fa130e8af26b659f2d3a0cb1114cd966a9b0e..f74a42883dd31972617d6bb3b293294fe19b0bdc 100644 --- a/lombok.config +++ b/lombok.config @@ -28,4 +28,5 @@ lombok.log.log4j.flagUsage = ERROR lombok.data.flagUsage = ERROR lombok.nonNull.exceptionType = IllegalArgumentException lombok.addLombokGeneratedAnnotation = true -lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier \ No newline at end of file +lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier +lombok.copyableAnnotations += net.devh.boot.grpc.client.inject.GrpcClient