From 8dc9cadbe173b96713affd33824df8295b20b7a0 Mon Sep 17 00:00:00 2001 From: aoezyetimoglu Date: Thu, 4 Nov 2021 11:59:26 +0100 Subject: [PATCH] RED-2560 Syngenta RFC: IUCLID Justification Form --- .../report/v1/server/model/ColoredText.java | 13 ++++ .../v1/server/model/ReportRedactionEntry.java | 1 + .../server/service/IuclidFunctionService.java | 59 ++++++++++++++++++ .../v1/server/service/PlaceholderService.java | 13 ++-- .../service/RedactionLogConverterService.java | 2 +- .../service/WordReportGenerationService.java | 44 ++++++++++--- .../RedactionReportIntegrationTest.java | 56 +++++++++++++++++ .../test/resources/files/redactionLog.json | 2 +- .../resources/templates/IUCLID_Template.docx | Bin 0 -> 4367 bytes 9 files changed, 176 insertions(+), 14 deletions(-) create mode 100644 redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ColoredText.java create mode 100644 redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/IuclidFunctionService.java create mode 100644 redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/templates/IUCLID_Template.docx diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ColoredText.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ColoredText.java new file mode 100644 index 0000000..d740345 --- /dev/null +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ColoredText.java @@ -0,0 +1,13 @@ +package com.iqser.red.service.redaction.report.v1.server.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@AllArgsConstructor +public class ColoredText { + private String text; + private String color; +} diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ReportRedactionEntry.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ReportRedactionEntry.java index 4a19ff4..706c291 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ReportRedactionEntry.java +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ReportRedactionEntry.java @@ -15,4 +15,5 @@ public class ReportRedactionEntry { private String justificationParagraph; private String justificationReason; private String excerpt; + private String value; } diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/IuclidFunctionService.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/IuclidFunctionService.java new file mode 100644 index 0000000..d64b132 --- /dev/null +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/IuclidFunctionService.java @@ -0,0 +1,59 @@ +package com.iqser.red.service.redaction.report.v1.server.service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Service; + +import com.iqser.red.service.redaction.report.v1.server.model.ReportRedactionEntry; + +@Service +public class IuclidFunctionService { + + public String computeIuclidFunction(List reportRedactionEntries, String filename) { + + Map> entriesPerJustification = new HashMap<>(); + reportRedactionEntries.forEach(reportRedactionEntry -> { + entriesPerJustification.computeIfAbsent(reportRedactionEntry.getJustification(), (x) -> new ArrayList<>()) + .add(reportRedactionEntry); + }); + + StringBuilder s = new StringBuilder(); + s.append(removeExtension(filename)); + s.append("\n\n\n"); + entriesPerJustification.keySet().forEach(key -> { + s.append("<[]>"); + s.append(key.replaceAll("\n", "\n<[]>")); + s.append("\n\n"); + s.append("relates to: "); + Iterator iterator = entriesPerJustification.get(key).iterator(); + while (iterator.hasNext()) { + ReportRedactionEntry entry = iterator.next(); + s.append("p").append(entry.getPage()); + s.append(" ").append(entry.getValue() != null ? entry.getValue() : "non-readable content"); + if (iterator.hasNext()) { + s.append(", "); + } + } + s.append("\n\n\n"); + + }); + + return s.toString(); + } + + + private String removeExtension(String fileName) { + + var index = fileName.lastIndexOf("."); + if (index > 0) { + return fileName.substring(0, index); + } else { + return fileName; + } + } + +} diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/PlaceholderService.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/PlaceholderService.java index 93dee16..8d7efbd 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/PlaceholderService.java +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/PlaceholderService.java @@ -1,12 +1,13 @@ package com.iqser.red.service.redaction.report.v1.server.service; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - import java.time.format.DateTimeFormatter; import java.util.List; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + @Slf4j @Service @RequiredArgsConstructor @@ -32,10 +33,12 @@ public class PlaceholderService { public static final String DOSSIER_NAME_PLACEHOLDER = "{{dossier.name}}"; + public static final String IUCLID_FUNCTION_PLACEHOLDER = "{{function.iuclidreport}}"; + public List getGeneralPlaceholders() { - return List.of(FILE_NAME_PLACEHOLDER, PAGE_PLACEHOLDER, PARAGRAPH_PLACEHOLDER, JUSTIFICATION_PLACEHOLDER, JUSTIFICATION_PARAGRAPH_PLACEHOLDER, JUSTIFICATION_REASON_PLACEHOLDER, EXCERPT_PLACEHOLDER, FORMAT_DATE_ISO_PLACEHOLDER, FORMAT_DATE_GER_PLACEHOLDER, FORMAT_DATE_ENG_PLACEHOLDER, FORMAT_TIME_ISO_PLACEHOLDER, DOSSIER_NAME_PLACEHOLDER); + return List.of(FILE_NAME_PLACEHOLDER, PAGE_PLACEHOLDER, PARAGRAPH_PLACEHOLDER, JUSTIFICATION_PLACEHOLDER, JUSTIFICATION_PARAGRAPH_PLACEHOLDER, JUSTIFICATION_REASON_PLACEHOLDER, EXCERPT_PLACEHOLDER, FORMAT_DATE_ISO_PLACEHOLDER, FORMAT_DATE_GER_PLACEHOLDER, FORMAT_DATE_ENG_PLACEHOLDER, FORMAT_TIME_ISO_PLACEHOLDER, DOSSIER_NAME_PLACEHOLDER, IUCLID_FUNCTION_PLACEHOLDER); } } diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/RedactionLogConverterService.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/RedactionLogConverterService.java index 0847e95..4fa87ec 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/RedactionLogConverterService.java +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/RedactionLogConverterService.java @@ -63,7 +63,7 @@ public class RedactionLogConverterService { .filter(lbm -> lbm.getReason().equalsIgnoreCase(entry.getLegalBasis())) .findAny() .map(LegalBasis::getDescription) - .orElse(""), checkTextForNull(entry.getTextBefore()) + entry.getValue() + checkTextForNull(entry.getTextAfter()))); + .orElse(""), checkTextForNull(entry.getTextBefore()) + entry.getValue() + checkTextForNull(entry.getTextAfter()), entry.getValue())); } } } diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/WordReportGenerationService.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/WordReportGenerationService.java index 61fd656..ad87f42 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/WordReportGenerationService.java +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/WordReportGenerationService.java @@ -10,10 +10,13 @@ import com.iqser.red.service.redaction.report.v1.api.model.ReportType; import com.iqser.red.service.redaction.report.v1.server.client.DossierAttributesClient; import com.iqser.red.service.redaction.report.v1.server.client.DossierAttributesConfigClient; import com.iqser.red.service.redaction.report.v1.server.client.FileAttributesConfigClient; +import com.iqser.red.service.redaction.report.v1.server.model.ColoredText; import com.iqser.red.service.redaction.report.v1.server.model.ImagePlaceholder; import com.iqser.red.service.redaction.report.v1.server.model.ReportRedactionEntry; import com.iqser.red.service.redaction.report.v1.server.storage.ReportStorageService; import lombok.RequiredArgsConstructor; + +import org.apache.commons.lang3.StringUtils; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.apache.poi.util.Units; import org.apache.poi.xwpf.usermodel.*; @@ -36,6 +39,7 @@ public class WordReportGenerationService { private final ReportStorageService reportStorageService; private final DossierAttributesClient dossierAttributesClient; private final DossierAttributesConfigClient dossierAttributesConfigClient; + private final IuclidFunctionService iuclidFunctionService; public byte[] generateReport(ReportType reportType, List reportEntries, @@ -93,7 +97,7 @@ public class WordReportGenerationService { } addTableRows(doc, reportEntries, fileStatus.getFilename()); for (String placeholder : placeholders) { - String placeholderValue = getPlaceholderValue(placeholder, dossier, fileStatus, fileAttributePlaceholders, dossierAttributesPlaceholder); + String placeholderValue = getPlaceholderValue(placeholder, dossier, fileStatus, fileAttributePlaceholders, dossierAttributesPlaceholder, reportEntries); if (placeholderValue != null) { replaceTextPlaceholders(doc, placeholder, placeholderValue); } @@ -123,7 +127,7 @@ public class WordReportGenerationService { private List getDefaultPlaceholders() { List defPlaceholders = new ArrayList<>(); - defPlaceholders.addAll(Arrays.asList(FILE_NAME_PLACEHOLDER, FORMAT_DATE_ISO_PLACEHOLDER, FORMAT_DATE_GER_PLACEHOLDER, FORMAT_DATE_ENG_PLACEHOLDER, FORMAT_TIME_ISO_PLACEHOLDER, DOSSIER_NAME_PLACEHOLDER)); + defPlaceholders.addAll(Arrays.asList(FILE_NAME_PLACEHOLDER, FORMAT_DATE_ISO_PLACEHOLDER, FORMAT_DATE_GER_PLACEHOLDER, FORMAT_DATE_ENG_PLACEHOLDER, FORMAT_TIME_ISO_PLACEHOLDER, DOSSIER_NAME_PLACEHOLDER, IUCLID_FUNCTION_PLACEHOLDER)); return defPlaceholders; } @@ -142,7 +146,8 @@ public class WordReportGenerationService { private String getPlaceholderValue(String placeholder, Dossier project, FileModel fileStatus, Map fileAttributePlaceholders, - Map dossierAttributesPlaceholders) { + Map dossierAttributesPlaceholders, + List reportRedactionEntries) { if (placeholder.equals(FORMAT_DATE_ISO_PLACEHOLDER)) { return OffsetDateTime.now().format(FORMAT_DATE_ISO); @@ -162,6 +167,9 @@ public class WordReportGenerationService { if (placeholder.equals(FILE_NAME_PLACEHOLDER)) { return fileStatus.getFilename(); } + if (placeholder.equals(IUCLID_FUNCTION_PLACEHOLDER)) { + return iuclidFunctionService.computeIuclidFunction(reportRedactionEntries, fileStatus.getFilename()); + } if (fileAttributePlaceholders.containsKey(placeholder)) { String id = fileAttributePlaceholders.get(placeholder); @@ -221,11 +229,24 @@ public class WordReportGenerationService { if (paragraphText.contains(search)) { String safeToUseInReplaceAllString = Pattern.quote(search); paragraphText = paragraphText.replaceAll(safeToUseInReplaceAllString, replace); - XWPFRun run = p.getRuns().get(0); - run.setText(paragraphText, 0); int size = p.getRuns().size(); - for (int i = 1; i < size; i++) { - p.removeRun(1); + for (int i = 0; i <= size; i++) { + p.removeRun(0); + } + if (paragraphText.contains("\n")) { + String[] stringsOnNewLines = paragraphText.split("\n"); + for (int i = 0; i < stringsOnNewLines.length; i++) { + p.insertNewRun(i); + XWPFRun newRun = p.getRuns().get(i); + String textForLine = stringsOnNewLines[i]; + ColoredText coloredText = getColor(textForLine); + newRun.setText(coloredText.getText()); + if (coloredText.getColor() != null) { + newRun.setTextHighlightColor(coloredText.getColor()); + } + newRun.addCarriageReturn(); + + } } } } @@ -316,6 +337,15 @@ public class WordReportGenerationService { run.setText(value); } + private ColoredText getColor(String textForLine) { + + if (textForLine.contains("<[]>"); + String text = StringUtils.substringAfter(textForLine, ">]>"); + return new ColoredText(text, color); + } + return new ColoredText(textForLine, null); + } private byte[] toByteArray(XWPFDocument doc) throws IOException { diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/test/java/com/iqser/red/service/redaction/report/v1/server/RedactionReportIntegrationTest.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/test/java/com/iqser/red/service/redaction/report/v1/server/RedactionReportIntegrationTest.java index 60d6ec8..1797807 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/test/java/com/iqser/red/service/redaction/report/v1/server/RedactionReportIntegrationTest.java +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/test/java/com/iqser/red/service/redaction/report/v1/server/RedactionReportIntegrationTest.java @@ -54,6 +54,8 @@ import com.iqser.red.service.redaction.report.v1.server.storage.ReportStorageSer import com.iqser.red.service.redaction.v1.model.RedactionLog; import com.iqser.red.storage.commons.service.StorageService; +import lombok.SneakyThrows; + @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) public class RedactionReportIntegrationTest { @@ -405,6 +407,60 @@ public class RedactionReportIntegrationTest { } } + @Test + @SneakyThrows + public void testIuclidReport() { + String dossierTemplateId = "dossierTemplateId"; + + ClassPathResource redactionLogResource = new ClassPathResource("files/redactionLog.json"); + + RedactionLog redactionLog = objectMapper.readValue(redactionLogResource.getInputStream(), RedactionLog.class); + + ClassPathResource legalBasisMappingResource = new ClassPathResource("files/legalBasisMapping.json"); + + List legalBasisMapping = objectMapper.readValue(legalBasisMappingResource.getInputStream(), new TypeReference<>() { + }); + + List reportEntries = redactionLogConverterService.convertAndSort(redactionLog, legalBasisMapping); + + + when(dossierAttributesConfigClient.getDossierAttributes(dossierTemplateId)).thenReturn(new ArrayList<>()); + + + when(dossierAttributesClient.getDossierAttributes("dossierId")).thenReturn(new ArrayList<>()); + + + when(fileAttributesConfigClient.getFileAttributeConfigs(dossierTemplateId)).thenReturn(new ArrayList<>()); + + + FileModel fileStatus = FileModel.builder().filename("filename").build(); + + Dossier project = Dossier.builder().id("dossierId").dossierName("projectName").build(); + + String templateId = "templateId"; + String storageId = "storageId"; + when(reportTemplateClient.getReportTemplate(dossierTemplateId, templateId)).thenReturn(ReportTemplate.builder() + .dossierTemplateId(dossierTemplateId) + .storageId(storageId) + .build()); + + ClassPathResource templateResource = new ClassPathResource("templates/IUCLID_Template.docx"); + when(reportStorageService.getReportTemplate(storageId)).thenReturn(IOUtils.toByteArray(templateResource.getInputStream())); + ReportTemplate reportTemplate = ReportTemplate.builder() + .dossierTemplateId("dossierTemplateId") + .templateId("templateId") + .fileName("fileName") + .storageId("storageId") + .uploadDate(OffsetDateTime.now()) + .build(); + byte[] report = wordReportGenerationService.generateReport(ReportType.WORD_SINGLE_FILE, reportEntries, dossierTemplateId, reportTemplate, fileStatus, project); + + try (FileOutputStream fileOutputStream = new FileOutputStream(getTemporaryDirectory() + "/iuclid_report.docx")) { + fileOutputStream.write(report); + } + } + + private static String getTemporaryDirectory() { String tmpdir = System.getProperty("java.io.tmpdir"); if (StringUtils.isNotBlank(tmpdir)) { diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/files/redactionLog.json b/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/files/redactionLog.json index aaef186..1aa54ed 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/files/redactionLog.json +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/files/redactionLog.json @@ -726,7 +726,7 @@ "value": "Syngenta Crop Protection AG", "reason": "Address found", "matchedRule": 2, - "legalBasis": "Article 39(e)(1) and Article 39(e)(2) of Regulation (EC) No 178/2002", + "legalBasis": "Article 63(2)(a) of Regulation (EC) No 1107/2009 (making reference to Article 39 of Regulation EC No 178/2002)", "redacted": true, "section": "1 Statement of subject matter and purpose for which this report\nhas been prepared and background information on the application", "color": [ diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/templates/IUCLID_Template.docx b/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/templates/IUCLID_Template.docx new file mode 100644 index 0000000000000000000000000000000000000000..3ed1fbc061516ba75115fe35c9c6c25ccb4e5cc4 GIT binary patch literal 4367 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 lAuPYcPtt