From 4c2addf00504c712472d52f2ea330c0bc2e1a369 Mon Sep 17 00:00:00 2001 From: OZGCloud <ozgcloud@mgm-tp.com> Date: Tue, 20 Dec 2022 14:51:28 +0100 Subject: [PATCH] OZG-3284 poc implementation for parsing protobuf input --- .../formcycle-adapter-impl/pom.xml | 19 ++++- .../eingang/formcycle/FormDataController.java | 21 ++++++ .../FormcycleAdapterApplication.java | 15 ++++ .../protobuf/ProtobufMessageConverter.java | 64 +++++++++++++++++ .../src/main/resources/application-local.yml | 3 + .../src/main/resources/application.yml | 33 +++++++++ .../formcycle/FormDataControllerITCase.java | 40 +++++++++++ .../formcycle/FormDataControllerTest.java | 70 +++++++++++++++++++ .../ProtobufMessageConverterTest.java | 48 +++++++++++++ .../org.junit.jupiter.api.extension.Extension | 1 + .../test/resources/junit-platform.properties | 1 + .../src/test/resources/log4j2.xml | 14 ++++ .../formcycle-adapter-interface/pom.xml | 4 +- .../src/main/protobuf/form-data.model.proto | 7 +- 14 files changed, 336 insertions(+), 4 deletions(-) create mode 100644 formcycle-adapter/formcycle-adapter-impl/src/main/java/de/itvsh/kop/eingang/formcycle/FormDataController.java create mode 100644 formcycle-adapter/formcycle-adapter-impl/src/main/java/de/itvsh/kop/eingang/formcycle/FormcycleAdapterApplication.java create mode 100644 formcycle-adapter/formcycle-adapter-impl/src/main/java/de/itvsh/kop/eingang/formcycle/common/protobuf/ProtobufMessageConverter.java create mode 100644 formcycle-adapter/formcycle-adapter-impl/src/main/resources/application-local.yml create mode 100644 formcycle-adapter/formcycle-adapter-impl/src/main/resources/application.yml create mode 100644 formcycle-adapter/formcycle-adapter-impl/src/test/java/de/itvsh/kop/eingang/formcycle/FormDataControllerITCase.java create mode 100644 formcycle-adapter/formcycle-adapter-impl/src/test/java/de/itvsh/kop/eingang/formcycle/FormDataControllerTest.java create mode 100644 formcycle-adapter/formcycle-adapter-impl/src/test/java/de/itvsh/kop/eingang/formcycle/common/protobuf/ProtobufMessageConverterTest.java create mode 100644 formcycle-adapter/formcycle-adapter-impl/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension create mode 100644 formcycle-adapter/formcycle-adapter-impl/src/test/resources/junit-platform.properties create mode 100644 formcycle-adapter/formcycle-adapter-impl/src/test/resources/log4j2.xml diff --git a/formcycle-adapter/formcycle-adapter-impl/pom.xml b/formcycle-adapter/formcycle-adapter-impl/pom.xml index 7a775fb4c..df115c8c8 100644 --- a/formcycle-adapter/formcycle-adapter-impl/pom.xml +++ b/formcycle-adapter/formcycle-adapter-impl/pom.xml @@ -32,11 +32,26 @@ <artifactId>formcycle-adapter</artifactId> <version>1.1.0-SNAPSHOT</version> </parent> - + <artifactId>formcycle-adapter-impl</artifactId> + <name>EM - Formcycle Adapter - Implementation</name> + + <properties> + <formcycle-interface.version>1.0.0-SNAPSHOT</formcycle-interface.version> + </properties> + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> - <name>EM - Formcycle Adapter - Implementation</name> + <dependency> + <groupId>de.itvsh.kop.eingangsadapter</groupId> + <artifactId>formcycle-adapter-interface</artifactId> + <version>${formcycle-interface.version}</version> + </dependency> + </dependencies> <build> <plugins> diff --git a/formcycle-adapter/formcycle-adapter-impl/src/main/java/de/itvsh/kop/eingang/formcycle/FormDataController.java b/formcycle-adapter/formcycle-adapter-impl/src/main/java/de/itvsh/kop/eingang/formcycle/FormDataController.java new file mode 100644 index 000000000..fe89919f8 --- /dev/null +++ b/formcycle-adapter/formcycle-adapter-impl/src/main/java/de/itvsh/kop/eingang/formcycle/FormDataController.java @@ -0,0 +1,21 @@ +package de.itvsh.kop.eingang.formcycle; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import de.itvsh.kop.eingangsadapter.formcycle.ConfirmationResponse; +import de.itvsh.kop.eingangsadapter.formcycle.ProtobufFormData; + +@Controller +@ResponseBody +@RequestMapping("formData") +class FormDataController { + + @PostMapping(consumes = "application/protobuf", produces = "application/protobuf") + public ConfirmationResponse receiveFormData(@RequestBody ProtobufFormData formData) { + return ConfirmationResponse.newBuilder().setVorgangNummer("abc").build(); + } +} diff --git a/formcycle-adapter/formcycle-adapter-impl/src/main/java/de/itvsh/kop/eingang/formcycle/FormcycleAdapterApplication.java b/formcycle-adapter/formcycle-adapter-impl/src/main/java/de/itvsh/kop/eingang/formcycle/FormcycleAdapterApplication.java new file mode 100644 index 000000000..3c51a5e52 --- /dev/null +++ b/formcycle-adapter/formcycle-adapter-impl/src/main/java/de/itvsh/kop/eingang/formcycle/FormcycleAdapterApplication.java @@ -0,0 +1,15 @@ +package de.itvsh.kop.eingang.formcycle; + +import java.util.TimeZone; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class FormcycleAdapterApplication { + + public static void main(String[] args) { + TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + SpringApplication.run(FormcycleAdapterApplication.class, args); + } +} diff --git a/formcycle-adapter/formcycle-adapter-impl/src/main/java/de/itvsh/kop/eingang/formcycle/common/protobuf/ProtobufMessageConverter.java b/formcycle-adapter/formcycle-adapter-impl/src/main/java/de/itvsh/kop/eingang/formcycle/common/protobuf/ProtobufMessageConverter.java new file mode 100644 index 000000000..eb803829f --- /dev/null +++ b/formcycle-adapter/formcycle-adapter-impl/src/main/java/de/itvsh/kop/eingang/formcycle/common/protobuf/ProtobufMessageConverter.java @@ -0,0 +1,64 @@ +package de.itvsh.kop.eingang.formcycle.common.protobuf; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Type; + +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.AbstractGenericHttpMessageConverter; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.stereotype.Component; + +import com.google.protobuf.CodedOutputStream; + +import de.itvsh.kop.common.errorhandling.TechnicalException; +import lombok.extern.log4j.Log4j2; + +@Component +@Log4j2 +public class ProtobufMessageConverter extends AbstractGenericHttpMessageConverter<Object> { + + public ProtobufMessageConverter() { + super(new MediaType(com.google.common.net.MediaType.PROTOBUF.type(), com.google.common.net.MediaType.PROTOBUF.subtype())); + } + + @Override + protected void writeInternal(Object t, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { + try { + var writeMethod = Class.forName(type.getTypeName()).getMethod("writeTo", CodedOutputStream.class); + CodedOutputStream out = CodedOutputStream.newInstance(outputMessage.getBody()); + writeMethod.invoke(t, out); + out.flush(); + } catch (NoSuchMethodException | SecurityException | ClassNotFoundException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException e) { + LOG.error("Error writing response", e); + throw new TechnicalException("Error writing response.", e); + } + + } + + @Override + public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { + try { + return readInternal(Class.forName(type.getTypeName()), inputMessage); + } catch (HttpMessageNotReadableException | ClassNotFoundException | IOException e) { + LOG.error("Error getting target class", e); + throw new TechnicalException("Error getting target class", e); + } + } + + @Override + protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { + try { + var parserMethod = clazz.getMethod("parseFrom", InputStream.class); + return parserMethod.invoke(null, inputMessage.getBody()); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + LOG.error("Error on reading protobuf data.", e); + throw new TechnicalException("Error on reading protobuf data.", e); + } + } +} diff --git a/formcycle-adapter/formcycle-adapter-impl/src/main/resources/application-local.yml b/formcycle-adapter/formcycle-adapter-impl/src/main/resources/application-local.yml new file mode 100644 index 000000000..f3fa5ad3f --- /dev/null +++ b/formcycle-adapter/formcycle-adapter-impl/src/main/resources/application-local.yml @@ -0,0 +1,3 @@ +server: + error: + include-stacktrace: always \ No newline at end of file diff --git a/formcycle-adapter/formcycle-adapter-impl/src/main/resources/application.yml b/formcycle-adapter/formcycle-adapter-impl/src/main/resources/application.yml new file mode 100644 index 000000000..0ce011207 --- /dev/null +++ b/formcycle-adapter/formcycle-adapter-impl/src/main/resources/application.yml @@ -0,0 +1,33 @@ +logging: + level: + ROOT: WARN + '[de.itvsh]': INFO + +server: + http2: + enabled: true + error: + include-stacktrace: never + +management: + server: + port: 8081 + health: + livenessState: + enabled: true + readinessState: + enabled: true + endpoint: + health: + group: + exploratory: + include: livenessState,readinessState,ping + show-details: always + probes: + enabled: true + prometheus: + enabled: true + endpoints: + web: + exposure: + include: health,prometheus \ No newline at end of file diff --git a/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/itvsh/kop/eingang/formcycle/FormDataControllerITCase.java b/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/itvsh/kop/eingang/formcycle/FormDataControllerITCase.java new file mode 100644 index 000000000..1ceebd58e --- /dev/null +++ b/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/itvsh/kop/eingang/formcycle/FormDataControllerITCase.java @@ -0,0 +1,40 @@ +package de.itvsh.kop.eingang.formcycle; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +import de.itvsh.kop.common.test.ITCase; +import lombok.SneakyThrows; + +@ITCase +@AutoConfigureMockMvc +class FormDataControllerITCase { + + @Autowired + private MockMvc mockMvc; + + @Nested + class ReceiveFormData { + + @Test + @SneakyThrows + void shouldProcessSuccessful() { + doPostRequest().andExpect(status().isOk()); + } + + @SneakyThrows + private ResultActions doPostRequest() { + return mockMvc.perform(post("/formData") + .contentType("application/protobuf") + .content(FormDataControllerTest.buildTestFormData())); + } + } + +} diff --git a/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/itvsh/kop/eingang/formcycle/FormDataControllerTest.java b/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/itvsh/kop/eingang/formcycle/FormDataControllerTest.java new file mode 100644 index 000000000..45cc2f972 --- /dev/null +++ b/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/itvsh/kop/eingang/formcycle/FormDataControllerTest.java @@ -0,0 +1,70 @@ +package de.itvsh.kop.eingang.formcycle; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.io.ByteArrayOutputStream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import de.itvsh.kop.eingang.formcycle.common.protobuf.ProtobufMessageConverter; +import de.itvsh.kop.eingangsadapter.formcycle.ConfirmationResponse; +import de.itvsh.kop.eingangsadapter.formcycle.ProtobufFormData; +import de.itvsh.kop.eingangsadapter.formcycle.ProtobufFormHeader; +import lombok.SneakyThrows; + +class FormDataControllerTest { + + @InjectMocks + private FormDataController controller; + + private MockMvc mockMvc; + + @BeforeEach + void init() { + mockMvc = MockMvcBuilders.standaloneSetup(controller) + .setMessageConverters(new ProtobufMessageConverter()) + .build(); + } + + @Nested + class ReceiveFormData { + @Test + void shouldReturnSuccess() throws Exception { + doPostRequest().andExpect(status().is2xxSuccessful()); + } + + @Test + @SneakyThrows + void shouldRespondeWithVorgangNummer() { + var confirmation = ConfirmationResponse.parseFrom( + doPostRequest().andReturn().getResponse().getContentAsByteArray()); + + assertThat(confirmation.getVorgangNummer()).isEqualTo("abc"); + } + + @SneakyThrows + private ResultActions doPostRequest() { + return mockMvc.perform(post("/formData") + .contentType("application/protobuf") + .content(buildTestFormData())); + } + } + + @SneakyThrows + static byte[] buildTestFormData() { + var header = ProtobufFormHeader.newBuilder().setFormName("test form 1").build(); + var formData = ProtobufFormData.newBuilder().setHeader(header).build(); + var out = new ByteArrayOutputStream(); + formData.writeTo(out); + return out.toByteArray(); + } + +} diff --git a/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/itvsh/kop/eingang/formcycle/common/protobuf/ProtobufMessageConverterTest.java b/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/itvsh/kop/eingang/formcycle/common/protobuf/ProtobufMessageConverterTest.java new file mode 100644 index 000000000..12d633e8f --- /dev/null +++ b/formcycle-adapter/formcycle-adapter-impl/src/test/java/de/itvsh/kop/eingang/formcycle/common/protobuf/ProtobufMessageConverterTest.java @@ -0,0 +1,48 @@ +package de.itvsh.kop.eingang.formcycle.common.protobuf; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.lang.reflect.Type; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Spy; +import org.springframework.http.HttpInputMessage; + +import de.itvsh.kop.common.errorhandling.TechnicalException; +import lombok.SneakyThrows; + +class ProtobufMessageConverterTest { + + @Spy + @InjectMocks + private ProtobufMessageConverter converter; + + @Nested + class TestRead { + + private HttpInputMessage inputMessage = mock(HttpInputMessage.class); + + @Test + @SneakyThrows + void shouldCallReadInternal() { + doReturn(null).when(converter).readInternal(any(), any()); + + converter.read(String.class, Object.class, inputMessage); + + verify(converter).readInternal(eq(String.class), same(inputMessage)); + } + + @Test + @SneakyThrows + void shouldThrowTechnicalException() { + var mockType = mock(Type.class); + when(mockType.getTypeName()).thenReturn("quatsch"); + + assertThatThrownBy(() -> converter.read(mockType, Object.class, inputMessage)).isInstanceOf(TechnicalException.class); + } + } +} diff --git a/formcycle-adapter/formcycle-adapter-impl/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/formcycle-adapter/formcycle-adapter-impl/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 000000000..79b126e6c --- /dev/null +++ b/formcycle-adapter/formcycle-adapter-impl/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +org.mockito.junit.jupiter.MockitoExtension \ No newline at end of file diff --git a/formcycle-adapter/formcycle-adapter-impl/src/test/resources/junit-platform.properties b/formcycle-adapter/formcycle-adapter-impl/src/test/resources/junit-platform.properties new file mode 100644 index 000000000..1cebb76d5 --- /dev/null +++ b/formcycle-adapter/formcycle-adapter-impl/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.extensions.autodetection.enabled = true \ No newline at end of file diff --git a/formcycle-adapter/formcycle-adapter-impl/src/test/resources/log4j2.xml b/formcycle-adapter/formcycle-adapter-impl/src/test/resources/log4j2.xml new file mode 100644 index 000000000..5d7001e1f --- /dev/null +++ b/formcycle-adapter/formcycle-adapter-impl/src/test/resources/log4j2.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + <Appenders> + <Console name="CONSOLE" target="SYSTEM_OUT"> + <PatternLayout pattern="[%-5level] %c{1.} %msg%n"/> + </Console> + </Appenders> + + <Loggers> + <Root level="WARN"> + <appender-ref ref="CONSOLE" /> + </Root> + </Loggers> +</configuration> \ No newline at end of file diff --git a/formcycle-adapter/formcycle-adapter-interface/pom.xml b/formcycle-adapter/formcycle-adapter-interface/pom.xml index bd2bbc1fc..f36a95342 100644 --- a/formcycle-adapter/formcycle-adapter-interface/pom.xml +++ b/formcycle-adapter/formcycle-adapter-interface/pom.xml @@ -33,8 +33,10 @@ <relativePath /> </parent> + <groupId>de.itvsh.kop.eingangsadapter</groupId> <artifactId>formcycle-adapter-interface</artifactId> <name>EM - Formcycle Adapter - Interface</name> + <version>1.0.0-SNAPSHOT</version> <dependencies> <dependency> @@ -45,7 +47,7 @@ <dependency> <groupId>de.itvsh.ozg.pluto</groupId> <artifactId>pluto-interface</artifactId> - <classifier>sources</classifier> + <classifier>sources</classifier> <version>1.3.0-SNAPSHOT</version> <scope>compile</scope> </dependency> diff --git a/formcycle-adapter/formcycle-adapter-interface/src/main/protobuf/form-data.model.proto b/formcycle-adapter/formcycle-adapter-interface/src/main/protobuf/form-data.model.proto index c295f666e..e689cff2a 100644 --- a/formcycle-adapter/formcycle-adapter-interface/src/main/protobuf/form-data.model.proto +++ b/formcycle-adapter/formcycle-adapter-interface/src/main/protobuf/form-data.model.proto @@ -31,9 +31,14 @@ option java_multiple_files = true; option java_package = "de.itvsh.kop.eingangsadapter.formcycle"; option java_outer_classname = "FormcycleFormDataProto"; +message ConfirmationResponse { + string vorgangNummer = 1; +} message ProtobufFormData { - de.itvsh.ozg.pluto.common.GrpcObject formData = 2; + ProtobufFormHeader header = 1; + ProtobufServiceKonto serviceKonto = 2; + de.itvsh.ozg.pluto.common.GrpcObject formData = 3; } message ProtobufFormHeader { -- GitLab