diff --git a/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsAttachmentsMapper.java b/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsAttachmentsMapper.java index 4781b63438dd76f538f64ae9eb58cde6d1357ffa..91c647211d5e3d4c390cabf8bf0ee6426e8816bd 100644 --- a/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsAttachmentsMapper.java +++ b/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsAttachmentsMapper.java @@ -29,14 +29,17 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; +import de.itvsh.kop.eingangsadapter.common.formdata.IncomingFile; import de.itvsh.kop.eingangsadapter.common.formdata.IncomingFileGroup; @Component class FormSolutionsAttachmentsMapper { + public static final String ZIP = "zip"; public static final String FILE_NAME_ZIP_ATTACHMENT = "attachments.zip"; public static final String ZIP_CONTENT_TYPE = "application/zip"; @@ -47,15 +50,32 @@ class FormSolutionsAttachmentsMapper { } List<IncomingFileGroup> mapZipRepresentation(Optional<String> encodedZip) { - var fileGroups = new ArrayList<IncomingFileGroup>(); - encodedZip.filter(StringUtils::isNoneEmpty).ifPresent(content -> { - var files = List.of(mapFile(decodeFile(content), ZIP_CONTENT_TYPE, FILE_NAME_ZIP_ATTACHMENT)); - fileGroups.add(IncomingFileGroup.builder() - .name(FILE_GROUP_ZIP_NAME) - .files(files) - .build()); - }); - - return fileGroups; + return encodedZip.filter(StringUtils::isNoneEmpty) + .map(this::buildZipFile) + .map(this::buildFileGroup) + .map(this::buildMutableList) + .orElseGet(ArrayList::new); + } + + private IncomingFileGroup buildFileGroup(IncomingFile zipFile) { + return IncomingFileGroup.builder() + .name(FILE_GROUP_ZIP_NAME) + .files(List.of(zipFile)) + .build(); + } + + private IncomingFile buildZipFile(String content) { + return IncomingFile.builder() + .id(UUID.randomUUID().toString()) + .contentStream(decodeFile(content)) + .contentType(ZIP_CONTENT_TYPE) + .name(FILE_NAME_ZIP_ATTACHMENT) + .build(); + } + + private List<IncomingFileGroup> buildMutableList(IncomingFileGroup fileGroup) { + var list = new ArrayList<IncomingFileGroup>(); + list.add(fileGroup); + return list; } } \ No newline at end of file diff --git a/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsFileMapperUtils.java b/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsFileMapperUtils.java index 7f7edd6d5c074cbd682a7088b2c6ca0bfe8af670..bb1a00cc86aaf1daf2e710a51a22e8d92377efc4 100644 --- a/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsFileMapperUtils.java +++ b/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsFileMapperUtils.java @@ -26,22 +26,12 @@ package de.itvsh.kop.eingangsadapter.formsolutions; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.Base64; -import java.util.UUID; -import de.itvsh.kop.eingangsadapter.common.formdata.IncomingFile; import lombok.AccessLevel; import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PRIVATE) class FormSolutionsFileMapperUtils { - static IncomingFile mapFile(InputStream data, String contentType, String fileName) { - return IncomingFile.builder() - .contentStream(data) - .contentType(contentType) - .name(fileName) - .id(UUID.randomUUID().toString()) - .build(); - } static InputStream decodeFile(String base64FileContent) { // TODO ins Dateisystem schreiben, anstatt in Memory halten diff --git a/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsRepresentationsMapper.java b/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsRepresentationsMapper.java index b6ad13f7cd390340bdb6d6d05fdebd105fb191da..586ecf147f7fb5a139c8a481790a346c75e2456d 100644 --- a/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsRepresentationsMapper.java +++ b/formsolutions-adapter/src/main/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsRepresentationsMapper.java @@ -23,22 +23,28 @@ */ package de.itvsh.kop.eingangsadapter.formsolutions; -import static de.itvsh.kop.eingangsadapter.formsolutions.FormSolutionsFileMapperUtils.*; - import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; +import de.itvsh.kop.common.errorhandling.TechnicalException; import de.itvsh.kop.eingangsadapter.common.formdata.IncomingFile; @Component class FormSolutionsRepresentationsMapper { + public static final String PDF = "pdf"; public static final String FILE_NAME_PDF_REP = "eingang.pdf"; public static final String PDF_CONTENT_TYPE = MediaType.APPLICATION_PDF_VALUE; @@ -47,21 +53,76 @@ class FormSolutionsRepresentationsMapper { public static final String FILE_NAME_JSON_REP = "form-data.json"; public static final String JSON_CONTENT_TYPE = MediaType.APPLICATION_JSON_VALUE; + public static final String TMP_FILE_PREFIX = "filecached-inputstream-fs"; + public static final String TMP_FILE_SUFFIX = ".ozg-cloud.tmp"; + List<IncomingFile> mapRepresentations(Map<String, Object> plainMap, Optional<String> json) { List<IncomingFile> representations = new ArrayList<>(); - Optional.ofNullable((String) plainMap.get(PDF)).filter(StringUtils::isNoneEmpty).ifPresent(data -> representations.add(mapPdfFile(data))); + Optional.ofNullable((String) plainMap.get(PDF)).filter(StringUtils::isNoneEmpty).ifPresent(data -> representations.add(buildPdfFile(data))); - json.ifPresent(jsonData -> representations.add(mapJsonFile(jsonData))); + json.ifPresent(jsonData -> representations.add(buildJsonFile(jsonData))); return representations; } - private IncomingFile mapJsonFile(String jsonData) { - return mapFile(new ByteArrayInputStream(jsonData.getBytes()), JSON_CONTENT_TYPE, FILE_NAME_JSON_REP); + private IncomingFile buildJsonFile(String jsonData) { + var size = getSize(new ByteArrayInputStream(jsonData.getBytes())); + return IncomingFile.builder() + .id(UUID.randomUUID().toString()) + .contentStream(new ByteArrayInputStream(jsonData.getBytes())) + .contentType(JSON_CONTENT_TYPE) + .name(FILE_NAME_JSON_REP) + .size(size) + .build(); + } + + private IncomingFile buildPdfFile(String data) { + var size = getSize(FormSolutionsFileMapperUtils.decodeFile(data)); + return IncomingFile.builder() + .id(UUID.randomUUID().toString()) + .contentStream(FormSolutionsFileMapperUtils.decodeFile(data)) + .contentType(PDF_CONTENT_TYPE) + .name(FILE_NAME_PDF_REP) + .size(size) + .build(); + } + + long getSize(InputStream contentStream) { + var path = writeFile(contentStream); + var size = getFileSize(path); + deleteTmpFile(path); + + return size; + } + + Path writeFile(InputStream contentStream) { + Path tmpFile; + try { + tmpFile = Files.createTempFile(TMP_FILE_PREFIX, TMP_FILE_SUFFIX); + tmpFile.toFile().deleteOnExit(); + + Files.copy(contentStream, tmpFile, StandardCopyOption.REPLACE_EXISTING); + contentStream.close(); + return tmpFile; + } catch (IOException e) { + throw new TechnicalException("Error writing file to temp file.", e); + } + } + + long getFileSize(Path path) { + try { + return Files.size(path); + } catch (IOException e) { + throw new TechnicalException("Error calculate size of tmp file.", e); + } } - private IncomingFile mapPdfFile(String data) { - return mapFile(decodeFile(data), PDF_CONTENT_TYPE, FILE_NAME_PDF_REP); + void deleteTmpFile(Path path) { + try { + Files.deleteIfExists(path); + } catch (IOException e) { + throw new TechnicalException("Error delete tmp file.", e); + } } } \ No newline at end of file diff --git a/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsFileMapperUtilsTest.java b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsFileMapperUtilsTest.java index 79db1ffd693cb73b920fb503813c5a31cf709775..a39dce7d3975d56de319c9d0d76d7810fe1d269b 100644 --- a/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsFileMapperUtilsTest.java +++ b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsFileMapperUtilsTest.java @@ -27,38 +27,19 @@ import static de.itvsh.kop.eingangsadapter.formsolutions.FormSolutionsFileMapper import static de.itvsh.kop.eingangsadapter.formsolutions.FormSolutionsFilesTestFactory.*; import static org.assertj.core.api.Assertions.*; -import java.io.ByteArrayInputStream; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import de.itvsh.kop.common.test.TestUtils; -import de.itvsh.kop.eingangsadapter.common.formdata.IncomingFile; import lombok.SneakyThrows; class FormSolutionsFileMapperUtilsTest { - @DisplayName("Test mapping File data to IncommingFIle") - @Nested - class TestFileMapping { - private static final String NAME = "test.txt"; - private static final String APPLICATION_TYPE = "application/text"; - private static final byte[] CONTENT_BYTES = "test".getBytes(); - - @Test - @SneakyThrows - void shouldMapFileData() { - IncomingFile file = FormSolutionsFileMapperUtils.mapFile(new ByteArrayInputStream(CONTENT_BYTES), APPLICATION_TYPE, NAME); - - assertThat(TestUtils.contentStreamToByteArray(file.getContentStream())).isEqualTo(CONTENT_BYTES); - assertThat(file.getContentType()).isEqualTo(APPLICATION_TYPE); - assertThat(file.getName()).isEqualTo(NAME); - } - } @DisplayName("Test decoding base64 encoded file") @Nested class TestDecodingBase64Content { + @Test @SneakyThrows void shouldDecodeFile() { @@ -67,4 +48,4 @@ class FormSolutionsFileMapperUtilsTest { assertThat(TestUtils.contentStreamToByteArray(decodedFileContentStream)).isEqualTo(ZIP_DECODED); } } -} +} \ No newline at end of file diff --git a/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsRepresentationsMapperTest.java b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsRepresentationsMapperTest.java index 69d3587df67f2090d3b55edc09469fcbf6447c22..6631cc4bc955a8db6ba6fe369149e6cb4903f276 100644 --- a/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsRepresentationsMapperTest.java +++ b/formsolutions-adapter/src/test/java/de/itvsh/kop/eingangsadapter/formsolutions/FormSolutionsRepresentationsMapperTest.java @@ -27,80 +27,116 @@ import static de.itvsh.kop.eingangsadapter.common.formdata.FormSolutionsTestFact import static de.itvsh.kop.eingangsadapter.formsolutions.FormSolutionsFilesTestFactory.*; import static de.itvsh.kop.eingangsadapter.formsolutions.FormSolutionsRepresentationsMapper.*; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.Optional; +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.mockito.Mock; +import org.mockito.Spy; +import de.itvsh.kop.common.errorhandling.TechnicalException; import de.itvsh.kop.common.test.TestUtils; import de.itvsh.kop.eingangsadapter.common.formdata.FormSolutionsTestFactory; import de.itvsh.kop.eingangsadapter.common.formdata.IncomingFile; +import de.itvsh.kop.eingangsadapter.common.formdata.IncomingFileTestFactory; import lombok.SneakyThrows; class FormSolutionsRepresentationsMapperTest { + + @Spy private FormSolutionsRepresentationsMapper mapper = new FormSolutionsRepresentationsMapper(); + @DisplayName("Map representations") @Nested - class TestRepresentationsMapping { + class TestMapRepresentations { + + @DisplayName("pdf") @Nested class TestPdfRepresentations { + @Test @SneakyThrows - void shouldParsePdf() { - var map = mapper.mapRepresentations(REPRESENTATIONS.getFormData(), Optional.of(JSON_CONTENT)); + void shouldSetContentStrean() { + var representation = mapRepresentationPdf(); - assertThat(TestUtils.contentStreamToByteArray(getRepresentation(map, 0).getContentStream())).isEqualTo(PDF_DECODED); + assertThat(TestUtils.contentStreamToByteArray(representation.getContentStream())).isEqualTo(PDF_DECODED); } @Test - void shouldSetPdfContentType() { - var map = mapper.mapRepresentations(REPRESENTATIONS.getFormData(), Optional.of(JSON_CONTENT)); + void shouldSetHaveContentType() { + var representation = mapRepresentationPdf(); - assertThat(getRepresentation(map, 0).getContentType()).isEqualTo(PDF_CONTENT_TYPE); + assertThat(representation.getContentType()).isEqualTo(PDF_CONTENT_TYPE); } @Test - void shouldSetPdfFileName() { - var map = mapper.mapRepresentations(REPRESENTATIONS.getFormData(), Optional.of(JSON_CONTENT)); + void shouldSetName() { + var representation = mapRepresentationPdf(); + + assertThat(representation.getName()).isEqualTo(FILE_NAME_PDF_REP); + } + + @Test + void shouldSetSize() { + var representation = mapRepresentationPdf(); + + assertThat(representation.getSize()).isEqualTo(FormSolutionsFilesTestFactory.PDF_DECODED.length); + } - assertThat(getRepresentation(map, 0).getName()).isEqualTo(FILE_NAME_PDF_REP); + private IncomingFile mapRepresentationPdf() { + return mapRepresentation(JSON_CONTENT).get(0); } } + @DisplayName("json") @Nested class TestJsonRepresentation { + @Test + void shouldHaveSize() { + var map = mapRepresentation(SIMPLE_JSON_DATA); + + assertThat(map).hasSize(2); + } + @Test @SneakyThrows - void shouldParseJson() { - var map = mapper.mapRepresentations(REPRESENTATIONS.getFormData(), Optional.of(SIMPLE_JSON_DATA)); + void shouldSetContentStream() { + var representation = mapRepresentationJson(); - assertThat(TestUtils.contentStreamToByteArray(getRepresentation(map, 1).getContentStream())) + assertThat(TestUtils.contentStreamToByteArray(representation.getContentStream())) .isEqualTo(FormSolutionsTestFactory.SIMPLE_JSON_DATA.getBytes()); } @Test - void shouldSetJsonContentType() { - var map = mapper.mapRepresentations(REPRESENTATIONS.getFormData(), Optional.of(SIMPLE_JSON_DATA)); + void shouldSetContentType() { + var representation = mapRepresentationJson(); - assertThat(getRepresentation(map, 1).getContentType()).isEqualTo(JSON_CONTENT_TYPE); + assertThat(representation.getContentType()).isEqualTo(JSON_CONTENT_TYPE); } @Test - void shouldSetJsonFileName() { - var map = mapper.mapRepresentations(REPRESENTATIONS.getFormData(), Optional.of(SIMPLE_JSON_DATA)); + void shouldSetName() { + var representation = mapRepresentationJson(); - assertThat(getRepresentation(map, 1).getName()).isEqualTo(FILE_NAME_JSON_REP); + assertThat(representation.getName()).isEqualTo(FILE_NAME_JSON_REP); } @Test - void shouldParse() { - var map = mapper.mapRepresentations(REPRESENTATIONS.getFormData(), Optional.of(SIMPLE_JSON_DATA)); + void shouldSetSize() { + var representation = mapRepresentationJson(); - assertThat(map).hasSize(2); + assertThat(representation.getSize()).isEqualTo(SIMPLE_JSON_DATA.getBytes().length); } @Test @@ -111,12 +147,67 @@ class FormSolutionsRepresentationsMapperTest { Optional.of(SIMPLE_JSON_DATA)); assertThat(map).hasSize(1); - assertThat(getRepresentation(map, 0).getContentType()).isEqualTo(JSON_CONTENT_TYPE); + assertThat(map.get(0).getContentType()).isEqualTo(JSON_CONTENT_TYPE); + } + + private IncomingFile mapRepresentationJson() { + return mapRepresentation(SIMPLE_JSON_DATA).get(1); } } + + private List<IncomingFile> mapRepresentation(String content) { + return mapper.mapRepresentations(REPRESENTATIONS.getFormData(), Optional.of(content)); + } + } + + @DisplayName("Get Size") + @Nested + class TestGetSize { + + @Mock + private InputStream stream; + @Mock + private Path path; + + @BeforeEach + void mock() { + doReturn(path).when(mapper).writeFile(any()); + doReturn(IncomingFileTestFactory.SIZE).when(mapper).getFileSize(any()); + doNothing().when(mapper).deleteTmpFile(any()); + } + + @Test + void shouldWriteFile() { + mapper.getSize(stream); + + verify(mapper).writeFile(stream); + } + + @Test + void shouldGetFileSize() { + mapper.getSize(stream); + + verify(mapper).getFileSize(path); + } + + @Test + void shouldDeleteTmpFile() { + mapper.getSize(stream); + + verify(mapper).deleteTmpFile(path); + } } - private IncomingFile getRepresentation(List<IncomingFile> map, int index) { - return map.get(index); + @Nested + class TestWriteFile { + + @Test + @SneakyThrows + void shouldThrowTechnicalException() { + InputStream closedStream = mock(InputStream.class); + when(closedStream.transferTo(any())).thenThrow(IOException.class); + + assertThatThrownBy(() -> mapper.writeFile(closedStream)).isInstanceOf(TechnicalException.class); + } } } diff --git a/semantik-adapter/src/main/java/de/itvsh/kop/eingangsadapter/semantik/enginebased/formsolutions/FormSolutionsFilesMapper.java b/semantik-adapter/src/main/java/de/itvsh/kop/eingangsadapter/semantik/enginebased/formsolutions/FormSolutionsFilesMapper.java index c8e1ad324f73bbd083b2802e95a046931f698ae5..bc88c2473afdf5fa77a67224f5e1061a58098e9a 100644 --- a/semantik-adapter/src/main/java/de/itvsh/kop/eingangsadapter/semantik/enginebased/formsolutions/FormSolutionsFilesMapper.java +++ b/semantik-adapter/src/main/java/de/itvsh/kop/eingangsadapter/semantik/enginebased/formsolutions/FormSolutionsFilesMapper.java @@ -46,10 +46,9 @@ class FormSolutionsFilesMapper implements FormSolutionsEngineBasedMapper { public FormData parseFormData(FormData srcFormData) { var formDataBuilder = FilesMapperHelper.getAttachedFileGroups(srcFormData) .map(this::collectAttachmentContent) - .map(fileGroups -> srcFormData.toBuilder() - .attachments(fileGroups) - .numberOfAttachments(FilesMapperHelper.countAttachedFiles(fileGroups))) + .map(fileGroups -> srcFormData.toBuilder().attachments(fileGroups).numberOfAttachments(FilesMapperHelper.countAttachedFiles(fileGroups))) .orElseGet(srcFormData::toBuilder); + FilesMapperHelper.getRepresentations(srcFormData) .ifPresent(representations -> formDataBuilder.representations(representations).numberOfRepresentations(representations.size())); return FilesMapperHelper.removeProcessedData(formDataBuilder.build()); @@ -61,7 +60,6 @@ class FormSolutionsFilesMapper implements FormSolutionsEngineBasedMapper { .map(file -> ZipAttachmentReader.from(file.getContentStream(), file.getName())) .map(this::mapToIncomingFileGroup) .toList(); - } protected IncomingFileGroup mapToIncomingFileGroup(ZipAttachmentReader zipAttachment) { @@ -71,16 +69,20 @@ class FormSolutionsFilesMapper implements FormSolutionsEngineBasedMapper { zipAttachment.deleteSourceFile(); } catch (RuntimeException e) { LOG.error("Cannot read source ZIP. Attach it as is.", e); - incomingFiles = List.of(IncomingFile.builder() - .name(zipAttachment.getSourceFileName()) - .size(zipAttachment.getSourceFileSize()) - .contentStream(zipAttachment.getSourceZipAsStream()) - .contentType(ZIP_CONTENT_TYPE) - .build()); + incomingFiles = List.of(buildIncomingZipFile(zipAttachment)); } return createIncomingFileGroup(incomingFiles); } + private IncomingFile buildIncomingZipFile(ZipAttachmentReader zipAttachment) { + return IncomingFile.builder() + .name(zipAttachment.getSourceFileName()) + .size(zipAttachment.getSourceFileSize()) + .contentStream(zipAttachment.getSourceZipAsStream()) + .contentType(ZIP_CONTENT_TYPE) + .build(); + } + protected IncomingFileGroup createIncomingFileGroup(Collection<IncomingFile> incomingFiles) { return IncomingFileGroup.builder().name(EXTRAHIERTE_ATTACHMENTS).files(incomingFiles).build(); }