diff --git a/common/src/main/java/de/itvsh/kop/eingangsadapter/common/formdata/FormData.java b/common/src/main/java/de/itvsh/kop/eingangsadapter/common/formdata/FormData.java index 2f72e711c1fca0e2d597b59f561be4d07c9499d7..fe13efc1aa1bacaff7a3fdae11a1e88dde64d0bf 100644 --- a/common/src/main/java/de/itvsh/kop/eingangsadapter/common/formdata/FormData.java +++ b/common/src/main/java/de/itvsh/kop/eingangsadapter/common/formdata/FormData.java @@ -16,21 +16,19 @@ import lombok.ToString; public class FormData { @NotNull - private UUID id; + @Builder.Default + private UUID id = UUID.randomUUID(); private FormHeader header; private ZustaendigeStelle zustaendigeStelle; - private Antragsteller antragsteller; private Map<String, Object> formData; - private List<IncomingFileGroup> attachments; - private int numberOfAttachments; - - private List<IncomingFile> representations; + private List<IncomingFileGroup> attachments; private int numberOfRepresentations; + private List<IncomingFile> representations; } diff --git a/common/src/main/java/de/itvsh/kop/eingangsadapter/common/formdata/FormHeader.java b/common/src/main/java/de/itvsh/kop/eingangsadapter/common/formdata/FormHeader.java index aa3b109c0ba73d8451eb014ce269846edf025b26..9bf458a0a703510f57675470000f9c7deda56dd7 100644 --- a/common/src/main/java/de/itvsh/kop/eingangsadapter/common/formdata/FormHeader.java +++ b/common/src/main/java/de/itvsh/kop/eingangsadapter/common/formdata/FormHeader.java @@ -15,7 +15,8 @@ public class FormHeader { private String requestId; - private ZonedDateTime createdAt; + @Builder.Default + private ZonedDateTime createdAt = ZonedDateTime.now(); private String formId; @@ -24,10 +25,8 @@ public class FormHeader { private String sender; private String customer; - private String customerId; private String client; - private String clientId; } diff --git a/formsolutions-adapter/pom.xml b/formsolutions-adapter/pom.xml index 05f81c4f5174de77be3077675256c2c4fe641692..097585c51b541480fa60f6909b0885fec5f098c5 100644 --- a/formsolutions-adapter/pom.xml +++ b/formsolutions-adapter/pom.xml @@ -37,6 +37,10 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-json</artifactId> + </dependency> <!-- tag::springws[] --> <dependency> diff --git a/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/EingangData.java b/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/EingangData.java new file mode 100644 index 0000000000000000000000000000000000000000..fc1b7554a4467fcb3817a0cd7f329e8be42236f0 --- /dev/null +++ b/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/EingangData.java @@ -0,0 +1,45 @@ +package de.itvsh.kop.eingangsadapter.formsolutions; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +class EingangData { + + @JsonProperty("assistant") + private FsFormData formData; + + private byte[] pdf; + +} + +@Getter +@Builder +class FsFormData { + private String identifier; + private List<FsPanel> panels; +} + +@Getter +@Builder +class FsPanel { + private String identifier; + private List<FsComponent> components; +} + +@Getter +@JsonIgnoreProperties("needed") +@Builder +class FsComponent { + private String identifier; + + @JsonProperty("stringValue") + private String value; + private List<FsComponent> components; +} \ No newline at end of file diff --git a/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/EingangDataMapper.java b/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/EingangDataMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..f4cb5349a0f53fc1743175810f3c63c469a83f74 --- /dev/null +++ b/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/EingangDataMapper.java @@ -0,0 +1,76 @@ +package de.itvsh.kop.eingangsadapter.formsolutions; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueCheckStrategy; + +import de.itvsh.kop.eingangsadapter.common.formdata.FormData; +import de.itvsh.kop.eingangsadapter.common.formdata.FormHeader; +import de.itvsh.kop.eingangsadapter.common.formdata.IncomingFile; + +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) +interface EingangDataMapper { + + static final Predicate<FsComponent> HAS_CONTENT = component -> Objects.nonNull(component.getComponents()) + || Objects.nonNull(component.getValue()); + static final Predicate<FsComponent> IS_NODE_COMPONENT = component -> Objects.isNull(component.getComponents()); + + static final String FILE_NAME_PDF_REP = "eingang.pdf"; + static final String PDF_CONTENT_TYPE = "application/pdf"; + + @Mapping(target = "id", ignore = true) + @Mapping(target = "antragsteller", ignore = true) + @Mapping(target = "zustaendigeStelle", ignore = true) + @Mapping(target = "attachments", ignore = true) + @Mapping(target = "numberOfAttachments", ignore = true) + @Mapping(target = "representations", expression = "java(mapPdfRepresentation(data.getPdf()))") + @Mapping(target = "numberOfRepresentations", constant = "1") + @Mapping(target = "header", expression = "java(mapHeader(data))") + FormData mapToFormData(EingangData data); + + default Map<String, Object> mapFormData(FsFormData formData) { + return Map.of(formData.getIdentifier(), mapPanels(formData.getPanels())); + } + + default Map<String, Object> mapPanels(List<FsPanel> panels) { + return panels.stream().collect(Collectors.toMap(FsPanel::getIdentifier, p -> mapComponents(p.getComponents()))); + } + + default Map<String, Object> mapComponents(List<FsComponent> components) { + return components.stream().filter(HAS_CONTENT).collect(Collectors.toMap(FsComponent::getIdentifier, this::mapComponent)); + } + + default Object mapComponent(FsComponent component) { + if (IS_NODE_COMPONENT.test(component)) { + return component.getValue(); + } else { + return mapComponents(component.getComponents()); + } + } + + default List<IncomingFile> mapPdfRepresentation(byte[] pdf) { + if (Objects.isNull(pdf) || pdf.length == 0) { + return Collections.emptyList(); + } + + return List.of(IncomingFile.builder() + .content(pdf) + .contentType(PDF_CONTENT_TYPE) + .size(pdf.length) + .name(FILE_NAME_PDF_REP) + .build()); + } + + default FormHeader mapHeader(EingangData data) { + return FormHeader.builder() + .formName(data.getFormData().getIdentifier()) + .build(); + } +} diff --git a/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/SendFormEndpoint.java b/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/SendFormEndpoint.java index 23313e2c5a22563a0bf77ecce26746dead74baac..bd8fd7be7a3406c276a776d8ab3ba1dca8eb6af3 100644 --- a/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/SendFormEndpoint.java +++ b/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/SendFormEndpoint.java @@ -1,19 +1,43 @@ package de.itvsh.kop.eingangsadapter.formsolutions; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ws.server.endpoint.annotation.Endpoint; import org.springframework.ws.server.endpoint.annotation.PayloadRoot; import org.springframework.ws.server.endpoint.annotation.RequestPayload; import org.springframework.ws.server.endpoint.annotation.ResponsePayload; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.itvsh.kop.eingangsadapter.common.errorhandling.TechnicalException; +import de.itvsh.kop.eingangsadapter.router.VorgangService; + @Endpoint public class SendFormEndpoint { + @Autowired + private ObjectMapper objectMapper; + @Autowired + private EingangDataMapper mapper; + @Autowired + private VorgangService service; + @PayloadRoot(namespace = WebServiceConfiguration.NAMESPACE_URI, localPart = "Request") @ResponsePayload public Response receiveForm(@RequestPayload Request request) { + service.createVorgang(mapper.mapToFormData(parseJsonData(request.getJSON()))); + return buildSuccessResponse(); } + EingangData parseJsonData(String jsonData) { + try { + return objectMapper.readValue(jsonData, EingangData.class); + } catch (JsonProcessingException e) { + throw new TechnicalException("Error parsing JSON from FromSolutions-Server: " + e.getCause().getMessage(), e); + } + } + private Response buildSuccessResponse() { var response = new Response(); diff --git a/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/EingangDataMapperTest.java b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/EingangDataMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5dd796aafd34d488971f109720d369c3b0167da2 --- /dev/null +++ b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/EingangDataMapperTest.java @@ -0,0 +1,125 @@ +package de.itvsh.kop.eingangsadapter.formsolutions; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; + +import de.itvsh.kop.eingangsadapter.common.formdata.IncomingFile; +import de.itvsh.kop.eingangsadapter.common.formdata.IncomingFileTestFactory; + +class EingangDataMapperTest { + + private EingangDataMapper mapper = spy(Mappers.getMapper(EingangDataMapper.class)); + + @Nested + class MapComponents { + @Test + void mapNodeComponent() { + var map = mapper.mapComponents(List.of(FsComponentTestFactory.createNode())); + + assertThat(map).containsEntry(FsComponentTestFactory.NODE_IDENTIFIER, FsComponentTestFactory.VALUE); + } + + @Test + void mapGroupComponent() { + var map = mapper.mapComponents(List.of(FsComponentTestFactory.createGroup())); + + assertThat(map).containsKey(FsComponentTestFactory.GROUP_IDENTIFIER); + @SuppressWarnings("unchecked") + var subMap = (Map<String, Object>) map.get(FsComponentTestFactory.GROUP_IDENTIFIER); + assertThat(subMap).containsEntry(FsComponentTestFactory.NODE_IDENTIFIER, FsComponentTestFactory.VALUE); + + } + } + + @Nested + class MapPanels { + @Test + void shoudContainIdentifier() { + var map = mapper.mapPanels(List.of(FsPanelTestFactory.create())); + + assertThat(map).containsKey(FsPanelTestFactory.IDENTIFIER); + } + + @Test + void shouldMapComponents() { + mapper.mapPanels(List.of(FsPanelTestFactory.create())); + + verify(mapper, times(2)).mapComponents(anyList()); + } + } + + @Nested + class MapFormData { + @Test + void shouldContainIdentifier() { + var map = mapper.mapFormData(FsFormDataTestFactory.create()); + + assertThat(map).containsKey(FsFormDataTestFactory.IDENTIFIER); + } + + @Test + void shouldMapPanel() { + mapper.mapFormData(FsFormDataTestFactory.create()); + + verify(mapper).mapPanels(anyList()); + } + } + + @Nested + class MapPdfRepresentation { + + @Test + void shouldHaveRepresentation() { + var formData = mapper.mapToFormData(EingangDataTestFactory.create()); + + assertThat(formData.getRepresentations()).hasSize(1); + } + + @Test + void shouldHaveName() { + var file = mapAndGetFile(); + + assertThat(file.getName()).isEqualTo(EingangDataMapper.FILE_NAME_PDF_REP); + } + + @Test + void shouldHaveContent() { + var file = mapAndGetFile(); + + assertThat(file.getContent()).isEqualTo(IncomingFileTestFactory.CONTENT); + } + + @Test + void shouldHaveContentType() { + var file = mapAndGetFile(); + + assertThat(file.getContentType()).isEqualTo(EingangDataMapper.PDF_CONTENT_TYPE); + } + + @Test + void shouldHaveSize() { + var file = mapAndGetFile(); + + assertThat(file.getSize()).isEqualTo(IncomingFileTestFactory.SIZE); + } + + @Test + void numberShoultBeOne() { + var formData = mapper.mapToFormData(EingangDataTestFactory.create()); + + assertThat(formData.getNumberOfRepresentations()).isEqualTo(1); + } + + private IncomingFile mapAndGetFile() { + return mapper.mapToFormData(EingangDataTestFactory.create()).getRepresentations().get(0); + } + } +} diff --git a/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/EingangDataTestFactory.java b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/EingangDataTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..b6497e782b4b7e5a318366bfd898a78207dd0338 --- /dev/null +++ b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/EingangDataTestFactory.java @@ -0,0 +1,16 @@ +package de.itvsh.kop.eingangsadapter.formsolutions; + +import de.itvsh.kop.eingangsadapter.common.formdata.IncomingFileTestFactory; + +class EingangDataTestFactory { + + static final EingangData create() { + return createBuilder().build(); + } + + static final EingangData.EingangDataBuilder createBuilder() { + return EingangData.builder() + .formData(FsFormDataTestFactory.create()) + .pdf(IncomingFileTestFactory.CONTENT); + } +} diff --git a/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FsComponentTestFactory.java b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FsComponentTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..afd5e028a2b14b22a83151764e04076bdb4105db --- /dev/null +++ b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FsComponentTestFactory.java @@ -0,0 +1,31 @@ +package de.itvsh.kop.eingangsadapter.formsolutions; + +import java.util.List; + +class FsComponentTestFactory { + + public static final String NODE_IDENTIFIER = "Name"; + public static final String GROUP_IDENTIFIER = "Kontakt"; + + public static final String VALUE = "Theo Test"; + + static FsComponent createNode() { + return createNodeBuilder().build(); + } + + static FsComponent.FsComponentBuilder createNodeBuilder() { + return FsComponent.builder() + .identifier(NODE_IDENTIFIER) + .value(VALUE); + } + + static FsComponent createGroup() { + return createGroupBuilder().build(); + } + + static FsComponent.FsComponentBuilder createGroupBuilder() { + return FsComponent.builder() + .identifier(GROUP_IDENTIFIER) + .components(List.of(createNode())); + } +} diff --git a/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FsFormDataTestFactory.java b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FsFormDataTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..6837672c68002469bcc70548c9f7b60292c168bd --- /dev/null +++ b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FsFormDataTestFactory.java @@ -0,0 +1,19 @@ +package de.itvsh.kop.eingangsadapter.formsolutions; + +import java.util.List; + +class FsFormDataTestFactory { + + public static final String IDENTIFIER = "Hamsterschein"; + + static FsFormData create() { + return createBuilder().build(); + } + + static FsFormData.FsFormDataBuilder createBuilder() { + return FsFormData.builder() + .identifier(IDENTIFIER) + .panels(List.of(FsPanelTestFactory.create())); + } + +} diff --git a/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FsPanelTestFactory.java b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FsPanelTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..3e5988f6ae1492bfd2c1a08c6c213546f69c9876 --- /dev/null +++ b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FsPanelTestFactory.java @@ -0,0 +1,18 @@ +package de.itvsh.kop.eingangsadapter.formsolutions; + +import java.util.List; + +class FsPanelTestFactory { + + static final String IDENTIFIER = "Kontaktdaten"; + + static FsPanel create() { + return createBuilder().build(); + } + + static FsPanel.FsPanelBuilder createBuilder() { + return FsPanel.builder() + .identifier(IDENTIFIER) + .components(List.of(FsComponentTestFactory.createGroup())); + } +} diff --git a/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/SendFormEndpointITCase.java b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/SendFormEndpointITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..dd91bd55391f1ef6f822b0328f5604b853fccf93 --- /dev/null +++ b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/SendFormEndpointITCase.java @@ -0,0 +1,172 @@ +package de.itvsh.kop.eingangsadapter.formsolutions; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles({ "local", "itcase" }) +class SendFormEndpointITCase { + + @Autowired + private SendFormEndpoint endpoint; + + @Nested + class ParseSimpleJsonData { + + private final String simpleJsonData = """ + { + "assistant": { + "identifier": "AS_123", + "panels": [{ + "identifier": "Panel_0_1", + "components": [ + { + "identifier": "Textfeld (einzeilig)", + "needed": true, + "stringValue": "kfjhkfjhk" + }, + { + "identifier": "Datums- / Uhrzeitfeld", + "needed": true, + "stringValue": "22.05.1996" + } + ]} + ] + } + }"""; + + @Test + void shouldParseData() { + var parsed = endpoint.parseJsonData(simpleJsonData); + + assertThat(parsed).isNotNull(); + } + + @Test + void shouldContainEingangData() { + var parsed = endpoint.parseJsonData(simpleJsonData); + + assertThat(parsed.getFormData()).isNotNull(); + } + + @Nested + class WithPanels { + @Test + void shouldContainPanel() { + var parsed = endpoint.parseJsonData(simpleJsonData); + + assertThat(parsed.getFormData().getPanels()).hasSize(1); + } + + @Test + void shouldHaveIdentifier() { + var panel = parseAndGetPanel(); + + assertThat(panel.getIdentifier()).isEqualTo("Panel_0_1"); + } + + @Test + void shouldHaveComponents() { + var panel = parseAndGetPanel(); + + assertThat(panel.getComponents()).hasSize(2); + } + + } + + private FsPanel parseAndGetPanel() { + return SendFormEndpointITCase.this.parseAndGetPanel(simpleJsonData); + } + } + + @Nested + class ParseNestedComponents { + // TODO move to file when TestUtils available + private final String nestedComponents = """ + { + "assistant": { + "identifier": "AS_123", + "panels": [{ + "identifier": "Panel_0_1", + "components": [{ + "identifier": "Objektgruppe[0]", + "needed": true, + "components": [ + { + "identifier": "Datums- / Uhrzeitfeld", + "needed": true, + "stringValue": "22.05.1996" + } + ] + }] + }] + } + }"""; + + @Test + void shouldParseData() { + var parsed = endpoint.parseJsonData(nestedComponents); + + assertThat(parsed).isNotNull(); + } + + @Test + void shouldHaveComponents() { + var panel = parseAndGetPanel(); + + assertThat(panel.getComponents()).hasSize(1); + } + + @Test + void shouldHaveIdentifier() { + var component = parseAndGetComponent(); + + assertThat(component.getIdentifier()).isEqualTo("Objektgruppe[0]"); + } + + @Test + void shouldHaveNestedComponents() { + var component = parseAndGetComponent(); + + assertThat(component.getComponents()).hasSize(1); + assertThat(component.getValue()).isNull(); + } + + private FsComponent parseAndGetComponent() { + return parseAndGetPanel().getComponents().get(0); + } + + private FsPanel parseAndGetPanel() { + return SendFormEndpointITCase.this.parseAndGetPanel(nestedComponents); + } + + } + + @Nested + class ParsePdfRepresentation { + // TODO move to file when TestUtils available + private final String pdfRepresentation = """ + { + "assistant":{}, + "pdf":"TG9yZW0gaXBzdW0=" + } + """; + + @Test + void shouldHavePdf() { + var parsed = endpoint.parseJsonData(pdfRepresentation); + + assertThat(parsed.getPdf()).isNotNull().isEqualTo("Lorem ipsum".getBytes()); + } + } + + private FsPanel parseAndGetPanel(String json) { + return endpoint.parseJsonData(json).getFormData().getPanels().get(0); + } + +} diff --git a/formsolutions-adapter/src/test/resources/application-itcase.yml b/formsolutions-adapter/src/test/resources/application-itcase.yml new file mode 100644 index 0000000000000000000000000000000000000000..0db8fef4679717244b61d2687f0a184659055ca9 --- /dev/null +++ b/formsolutions-adapter/src/test/resources/application-itcase.yml @@ -0,0 +1,4 @@ +spring: + jackson: + deserialization: + fail-on-unknown-properties: true \ No newline at end of file diff --git a/pom.xml b/pom.xml index bf3c2894dc122650133242326f0eb5b385cf1444..9df9a2ea657cd2d02ad2ca9fa17718010f7e6a9e 100644 --- a/pom.xml +++ b/pom.xml @@ -144,7 +144,7 @@ -Amapstruct.defaultComponentModel=spring </compilerArg> <compilerArg> - -Amapstruct.unmappedTargetPolicy=IGNORE + -Amapstruct.unmappedTargetPolicy=WARN </compilerArg> </compilerArgs> </configuration>