Skip to content
Snippets Groups Projects
Commit 4c2addf0 authored by OZGCloud's avatar OZGCloud
Browse files

OZG-3284 poc implementation for parsing protobuf input

parent eb83b123
No related branches found
No related tags found
No related merge requests found
Showing
with 336 additions and 4 deletions
......@@ -34,9 +34,24 @@
</parent>
<artifactId>formcycle-adapter-impl</artifactId>
<name>EM - Formcycle Adapter - Implementation</name>
<properties>
<formcycle-interface.version>1.0.0-SNAPSHOT</formcycle-interface.version>
</properties>
<name>EM - Formcycle Adapter - Implementation</name>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>de.itvsh.kop.eingangsadapter</groupId>
<artifactId>formcycle-adapter-interface</artifactId>
<version>${formcycle-interface.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
......
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();
}
}
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);
}
}
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);
}
}
}
server:
error:
include-stacktrace: always
\ No newline at end of file
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
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()));
}
}
}
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();
}
}
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);
}
}
}
org.mockito.junit.jupiter.MockitoExtension
\ No newline at end of file
junit.jupiter.extensions.autodetection.enabled = true
\ No newline at end of file
<?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
......@@ -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>
......
......@@ -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 {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment