diff --git a/formcycle-adapter/formcycle-adapter-impl/pom.xml b/formcycle-adapter/formcycle-adapter-impl/pom.xml index 7a775fb4caaea3d88bfe603f25c96166088612a4..df115c8c84696793b7bc0896c0322be1ff13eb25 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 0000000000000000000000000000000000000000..fe89919f8bf40fa9394218141e1c0024c053ee60 --- /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 0000000000000000000000000000000000000000..3c51a5e5218f10f02e157c0f3691bef494aeff20 --- /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 0000000000000000000000000000000000000000..eb803829f86fadcc74f3f6b1b4d3b3ed1286cecd --- /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 0000000000000000000000000000000000000000..f3fa5ad3f6f88fa90d458695866b7fa0b640ba0d --- /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 0000000000000000000000000000000000000000..0ce01120746bab21cfaa914eb5c4e4ca028f0c18 --- /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 0000000000000000000000000000000000000000..1ceebd58e6665495264b9cc10ac8a5e5250a833c --- /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 0000000000000000000000000000000000000000..45cc2f972e7b536555586920f0f0e6f4ccf86af5 --- /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 0000000000000000000000000000000000000000..12d633e8ff86c9c1a59b964af7764385c2963e2d --- /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 0000000000000000000000000000000000000000..79b126e6cdb86bec1f4f08c205de8961bde1934a --- /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 0000000000000000000000000000000000000000..1cebb76d5a58ac034b2627d12411d82d1e85821e --- /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 0000000000000000000000000000000000000000..5d7001e1f9186d197a2d301d3910c9d73ed05d15 --- /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 bd2bbc1fc0639852b34408f6ef4b7afe3887abe2..f36a95342b34576d8b625e92f5d833947f92bbba 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 c295f666ed514faf64f12e592fa094f180fc2826..e689cff2afe777e08ce0ee90d832a8ca558a4abd 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 {