From 93fcc061f94a33d4c23175291c922c4af95225c5 Mon Sep 17 00:00:00 2001 From: deiflaender Date: Fri, 20 May 2022 14:46:13 +0200 Subject: [PATCH] RED-4036: Reenabled all replacememts in excel --- .../pom.xml | 5 - .../report/v1/server/model/ExcelCell.java | 13 + .../report/v1/server/model/ExcelModel.java | 11 +- ...java => ExcelReportGenerationService.java} | 238 ++++++++++-------- .../server/service/IuclidFunctionService.java | 3 + .../service/ReportGenerationService.java | 62 +++-- .../server/service/ReportMessageReceiver.java | 2 +- .../service/WordReportGenerationService.java | 41 ++- .../RedactionReportIntegrationTest.java | 69 +++-- .../Excel Report_PlaceholderTest.xlsx | Bin 0 -> 9671 bytes .../test/resources/templates/TestReport.xlsx | Bin 4966 -> 0 bytes .../test/resources/templates/TestReport2.xlsx | Bin 4950 -> 0 bytes 12 files changed, 245 insertions(+), 199 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/ExcelCell.java rename redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/{ExcelTemplateReportGenerationService.java => ExcelReportGenerationService.java} (58%) create mode 100644 redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/templates/Excel Report_PlaceholderTest.xlsx delete mode 100644 redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/templates/TestReport.xlsx delete mode 100644 redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/templates/TestReport2.xlsx diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/pom.xml b/redaction-report-service-v1/redaction-report-service-server-v1/pom.xml index 3363feb..7d2a881 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/pom.xml +++ b/redaction-report-service-v1/redaction-report-service-server-v1/pom.xml @@ -54,11 +54,6 @@ 5.0.0 - - org.springframework.boot - spring-boot-starter-aop - - org.springframework.boot spring-boot-starter-amqp diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ExcelCell.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ExcelCell.java new file mode 100644 index 0000000..9e9027f --- /dev/null +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ExcelCell.java @@ -0,0 +1,13 @@ +package com.iqser.red.service.redaction.report.v1.server.model; + +import org.apache.poi.ss.usermodel.Cell; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class ExcelCell { + + private Cell cell; + private int columnWidth; + private int rowHeights; +} diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ExcelModel.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ExcelModel.java index cef2952..274c746 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ExcelModel.java +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ExcelModel.java @@ -1,6 +1,8 @@ package com.iqser.red.service.redaction.report.v1.server.model; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.function.Function; @@ -16,9 +18,12 @@ import lombok.NoArgsConstructor; public class ExcelModel { private Map> placeholderCellPos; - private int placeholderRow; + private int redactionPlaceholderRow; private Map cellWidths = new HashMap<>(); - private Map cellsToCopy = new HashMap<>(); - private boolean copyDone; + private Map cellsToCopyBeforeRedactionPlaceholderRow = new HashMap<>(); + private Map cellsToCopyAfterRedactionPlaceholderRow = new HashMap<>(); + + private List writtenRows = new ArrayList<>(); + private boolean rowsBeforeRedactionEntryRowsAdded; } diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelTemplateReportGenerationService.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelReportGenerationService.java similarity index 58% rename from redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelTemplateReportGenerationService.java rename to redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelReportGenerationService.java index 7b694de..89297e3 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelTemplateReportGenerationService.java +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelReportGenerationService.java @@ -25,6 +25,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.time.OffsetDateTime; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -37,11 +38,6 @@ import java.util.regex.Pattern; import javax.imageio.ImageIO; -import com.iqser.red.service.redaction.report.v1.server.model.CellIdentifier; -import com.iqser.red.service.redaction.report.v1.server.model.ExcelModel; -import com.iqser.red.service.redaction.report.v1.server.model.ImagePlaceholder; -import com.iqser.red.service.redaction.report.v1.server.model.PlaceholderModel; - import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.ClientAnchor; @@ -51,14 +47,17 @@ import org.apache.poi.ss.usermodel.Picture; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.xssf.streaming.SXSSFWorkbook; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.stereotype.Service; -import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.Dossier; import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileModel; -import com.iqser.red.service.redaction.report.v1.server.model.ReportRedactionEntry; +import com.iqser.red.service.redaction.report.v1.server.model.CellIdentifier; +import com.iqser.red.service.redaction.report.v1.server.model.ExcelModel; +import com.iqser.red.service.redaction.report.v1.server.model.ImagePlaceholder; import com.iqser.red.service.redaction.report.v1.server.model.PlaceholderInput; +import com.iqser.red.service.redaction.report.v1.server.model.PlaceholderModel; +import com.iqser.red.service.redaction.report.v1.server.model.ReportRedactionEntry; +import io.micrometer.core.annotation.Timed; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -66,71 +65,94 @@ import lombok.extern.slf4j.Slf4j; @Slf4j @Service @RequiredArgsConstructor -@SuppressWarnings("PMD") -public class ExcelTemplateReportGenerationService { +public class ExcelReportGenerationService { - @SuppressWarnings("checkstyle:ParameterAssignment") - public ExcelModel generateReport(List reportEntries, PlaceholderModel placeholderModel, - String reportTemplateName, SXSSFWorkbook workbook, FileModel fileStatus, - ExcelModel excelModel) { + @Timed("generateExcelReport") + public void generateExcelReport(List reportEntries, PlaceholderModel placeholderModel, + String reportTemplateName, SXSSFWorkbook workbook, String dossierName, + FileModel fileModel, ExcelModel excelModel, boolean isLastFile) { long start = System.currentTimeMillis(); try { + for (Sheet sheet : workbook) { - if(!excelModel.isCopyDone()) { + + if (!excelModel.isRowsBeforeRedactionEntryRowsAdded()) { + for (Map.Entry colummWidthEntry : excelModel.getCellWidths().entrySet()) { sheet.setColumnWidth(colummWidthEntry.getKey(), colummWidthEntry.getValue()); } - Set createdRows = new HashSet<>(); - for (Map.Entry cellsToCopyEntry : excelModel.getCellsToCopy().entrySet()) { - if (!createdRows.contains(cellsToCopyEntry.getKey().getRowIndex())) { - sheet.createRow(cellsToCopyEntry.getKey().getRowIndex()); - createdRows.add(cellsToCopyEntry.getKey().getRowIndex()); - } - - var createdCell = sheet.getRow(cellsToCopyEntry.getKey().getRowIndex()) - .createCell(cellsToCopyEntry.getKey().getColumnIndex()); - createdCell.setCellValue(cellsToCopyEntry.getValue().getStringCellValue()); - CellStyle newCellStyle = workbook.createCellStyle(); - newCellStyle.cloneStyleFrom(cellsToCopyEntry.getValue().getCellStyle()); - createdCell.setCellStyle(newCellStyle); - } - excelModel.setCopyDone(true); + addRows(workbook, sheet, excelModel.getCellsToCopyBeforeRedactionPlaceholderRow(), 0, placeholderModel, dossierName, fileModel.getFilename()); + excelModel.setRowsBeforeRedactionEntryRowsAdded(true); } - excelModel = addEntryRows(sheet, reportEntries, fileStatus.getFilename(), excelModel, placeholderModel); + addRedactionEntryRows(sheet, reportEntries, fileModel.getFilename(), excelModel, placeholderModel); -// for (ImagePlaceholder imagePlaceholder : placeholderModel.getImagePlaceholders()){ -// replacePlaceholdersForImagePlaceholder(workbook, sheet, imagePlaceholder); -// } -// for (String placeholder : placeholderModel.getPlaceholders()) { -// String placeholderValue = getPlaceholderValue(placeholder, dossier, fileStatus, placeholderModel.getFileAttributePlaceHolders(), placeholderModel.getDossierAttributesPlaceholder()); -// if (placeholderValue != null) { -// replacePlaceholdersForTextPlaceholder(sheet, placeholder, placeholderValue); -// } -// } + if (isLastFile) { + addRows(workbook, sheet, excelModel.getCellsToCopyAfterRedactionPlaceholderRow(), excelModel.getWrittenRows() + .size(), placeholderModel, dossierName, fileModel.getFilename()); + } } - long end = System.currentTimeMillis(); - - log.info("Report Generation took: {} for file with id {}, pageCount: {}, redactionLogEntryCount: {}, reportName: {}, className: {}", end - start, fileStatus.getId(), fileStatus.getNumberOfPages(), reportEntries.size(), reportTemplateName, getClass().getSimpleName()); + log.info("Report Generation took: {} for file with id {}, pageCount: {}, redactionLogEntryCount: {}, reportName: {}, className: {}", System.currentTimeMillis() - start, fileModel.getId(), fileModel.getNumberOfPages(), reportEntries.size(), reportTemplateName, getClass().getSimpleName()); } catch (Exception e) { throw new RuntimeException(e); } - - return excelModel; } - public ExcelModel caculateExcelModel(Sheet sheet) { + @SneakyThrows + @Timed("excel-addRows") + private void addRows(SXSSFWorkbook workbook, Sheet sheet, Map copiedCells, + int numberOfRowsToShift, PlaceholderModel placeholderModel, String dossierName, + String filename) { + + Set createdCopyRows = new HashSet<>(); + for (Map.Entry cellsToCopyEntry : copiedCells.entrySet()) { + + int indexToAddRow = cellsToCopyEntry.getKey().getRowIndex() + numberOfRowsToShift; + + if (!createdCopyRows.contains(indexToAddRow)) { + sheet.createRow(indexToAddRow); + createdCopyRows.add(indexToAddRow); + } + + var createdCell = sheet.getRow(indexToAddRow).createCell(cellsToCopyEntry.getKey().getColumnIndex()); + createdCell.setCellValue(cellsToCopyEntry.getValue().getStringCellValue()); + CellStyle newCellStyle = workbook.createCellStyle(); + newCellStyle.cloneStyleFrom(cellsToCopyEntry.getValue().getCellStyle()); + createdCell.setCellStyle(newCellStyle); + createdCell.getRow().setHeight(cellsToCopyEntry.getValue().getRow().getHeight()); + + replacePlaceholders(createdCell, placeholderModel, dossierName, filename); + replacePlaceholdersForImagePlaceholder(workbook, sheet, createdCell, placeholderModel); + } + } + + + private void replacePlaceholders(Cell cell, PlaceholderModel placeholderModel, String dossierName, + String filename) { + + for (String placeholder : placeholderModel.getPlaceholders()) { + String placeholderValue = getPlaceholderValue(placeholder, dossierName, filename, placeholderModel); + if (placeholderValue != null) { + replacePlaceholdersForCell(cell, placeholder, placeholderValue); + } + } + } + + + @Timed("calculateExcelModel") + public ExcelModel calculateExcelModel(Sheet sheet) { long start = System.currentTimeMillis(); Map> placeholderCellPos = new HashMap<>(); Map columnWidths = new HashMap<>(); - Map cellsToCopy = new HashMap<>(); + Map cellsToCopyBeforePlaceholderRow = new HashMap<>(); + Map cellsToCopyAfterPlaceholderRow = new HashMap<>(); int placeholderRow = -1; for (int j = 0; j < sheet.getLastRowNum() + 1; j++) { Row actualRow = sheet.getRow(j); @@ -138,42 +160,51 @@ public class ExcelTemplateReportGenerationService { for (int i = 0; i < actualRow.getLastCellNum(); i++) { Cell cell = sheet.getRow(j).getCell(i); if (cell != null) { + int columnWidth = sheet.getColumnWidth(i); + columnWidths.put(i, columnWidth); if (containsRedactionPlaceholder(cell.getStringCellValue())) { - int columnWidth = sheet.getColumnWidth(i); - columnWidths.put(i, columnWidth); + placeholderCellPos.put(i, getFunctionForPlaceHolder(cell.getStringCellValue())); placeholderRow = j; } else { - cellsToCopy.put(new CellIdentifier(j, i), cell); + if (placeholderRow == -1) { + cellsToCopyBeforePlaceholderRow.put(new CellIdentifier(j, i), cell); + } else { + cellsToCopyAfterPlaceholderRow.put(new CellIdentifier(j, i), cell); + } } } } } } log.debug("Calculate Placeholder Cells took: {}", System.currentTimeMillis() - start); - return new ExcelModel(placeholderCellPos, placeholderRow, columnWidths, cellsToCopy, false); + return new ExcelModel(placeholderCellPos, placeholderRow, columnWidths, cellsToCopyBeforePlaceholderRow, cellsToCopyAfterPlaceholderRow, new ArrayList<>(), false); } - @SuppressWarnings("checkstyle:ParameterAssignment") - private ExcelModel addEntryRows(Sheet sheet, List reportEntries, String filename, ExcelModel excelModel, PlaceholderModel placeholderModel) { + @Timed("excel-addRedactionEntryRows") + private void addRedactionEntryRows(Sheet sheet, List reportEntries, String filename, + ExcelModel excelModel, PlaceholderModel placeholderModel) { long start = System.currentTimeMillis(); - AtomicInteger rowIndex = new AtomicInteger(excelModel.getPlaceholderRow()); + AtomicInteger rowIndex = new AtomicInteger(excelModel.getRedactionPlaceholderRow()); Map> placeholderCellPos = excelModel.getPlaceholderCellPos(); reportEntries.forEach(entry -> { + sheet.createRow(rowIndex.get()); + excelModel.getWrittenRows().add(rowIndex.get()); for (Map.Entry> entry1 : placeholderCellPos.entrySet()) { Cell cell = sheet.getRow(rowIndex.get()).createCell(entry1.getKey()); - cell.setCellValue(entry1.getValue().apply(new PlaceholderInput(filename, entry, placeholderModel, null))); + cell.setCellValue(entry1.getValue() + .apply(new PlaceholderInput(filename, entry, placeholderModel, null))); } rowIndex.getAndIncrement(); }); - log.debug("Adding rows took: {}", System.currentTimeMillis() - start); - excelModel.setPlaceholderRow(rowIndex.getAndIncrement()); - return excelModel; + excelModel.setRedactionPlaceholderRow(rowIndex.getAndIncrement()); + + log.debug("Adding rows took: {}", System.currentTimeMillis() - start); } @@ -218,61 +249,53 @@ public class ExcelTemplateReportGenerationService { .replaceAll("\n", " ") .replaceAll(" ", " ") : ""; } - // TODO in case placeholder is invalid -> do not replace with empty string but throw previus exception return (input) -> ""; } - private void replacePlaceholdersForTextPlaceholder(Sheet sheet, String search, String replace) { + private void replacePlaceholdersForCell(Cell cell, String search, String replace) { - for (Row row : sheet) { - for (Cell cell : row) { - if (cell.getStringCellValue().contains(search)) { - String safeToUseInReplaceAllString = Pattern.quote(search); - String escapedReplace = Matcher.quoteReplacement(replace); - String str = cell.getStringCellValue().replaceAll(safeToUseInReplaceAllString, escapedReplace); - cell.setCellValue(str); - } - } + if (cell.getStringCellValue().contains(search)) { + String safeToUseInReplaceAllString = Pattern.quote(search); + String escapedReplace = Matcher.quoteReplacement(replace); + String str = cell.getStringCellValue().replaceAll(safeToUseInReplaceAllString, escapedReplace); + cell.setCellValue(str); } } - private void replacePlaceholdersForImagePlaceholder(XSSFWorkbook workbook, Sheet sheet, - ImagePlaceholder imagePlaceholder) throws IOException { + private void replacePlaceholdersForImagePlaceholder(SXSSFWorkbook workbook, Sheet sheet, Cell cell, + PlaceholderModel placeholderModel) throws IOException { - for (Row row : sheet) { - for (Cell cell : row) { - if (cell.getStringCellValue().contains(imagePlaceholder.getPlaceholder())) { - try (ByteArrayInputStream is = new ByteArrayInputStream(imagePlaceholder.getImage())) { + for (ImagePlaceholder imagePlaceholder : placeholderModel.getImagePlaceholders()) { -// ByteArrayInputStream scaledImage = getScaledImage(is, PixelUtil.widthUnits2Pixel((short) sheet.getColumnWidth(cell.getColumnIndex())), PixelUtil.heightUnits2Pixel(cell.getRow().getHeight())); -// is.reset(); + if (cell.getStringCellValue().contains(imagePlaceholder.getPlaceholder())) { + try (ByteArrayInputStream is = new ByteArrayInputStream(imagePlaceholder.getImage())) { - double factor = calculateScale(is, PixelUtil.widthUnits2Pixel((short) sheet.getColumnWidth(cell.getColumnIndex())), PixelUtil.heightUnits2Pixel(cell.getRow() - .getHeight())); - is.reset(); + double factor = calculateScale(is, PixelUtil.widthUnits2Pixel((short) sheet.getColumnWidth(cell.getColumnIndex())), PixelUtil.heightUnits2Pixel(cell.getRow() + .getHeight())); + is.reset(); - int pictureIdx = workbook.addPicture(is, XSSFWorkbook.PICTURE_TYPE_JPEG); - is.reset(); + int pictureIdx = workbook.addPicture(is.readAllBytes(), SXSSFWorkbook.PICTURE_TYPE_JPEG); + is.reset(); - //Returns an object that handles instantiating concrete classes - CreationHelper helper = workbook.getCreationHelper(); - //Create an anchor that is attached to the worksheet - ClientAnchor anchor = helper.createClientAnchor(); - anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE); + //Returns an object that handles instantiating concrete classes + CreationHelper helper = workbook.getCreationHelper(); + //Create an anchor that is attached to the worksheet + ClientAnchor anchor = helper.createClientAnchor(); + anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE); - anchor.setCol1(cell.getColumnIndex()); - anchor.setRow1(cell.getRowIndex()); + anchor.setCol1(cell.getColumnIndex()); + anchor.setRow1(cell.getRowIndex()); - //Creates the top-level drawing patriarch. - Drawing drawing = sheet.createDrawingPatriarch(); + //Creates the top-level drawing patriarch. + Drawing drawing = sheet.createDrawingPatriarch(); - Picture picture = drawing.createPicture(anchor, pictureIdx); - picture.resize(factor); + Picture picture = drawing.createPicture(anchor, pictureIdx); + picture.resize(factor); + + cell.setCellValue(""); - cell.setCellValue(""); - } } } } @@ -301,9 +324,8 @@ public class ExcelTemplateReportGenerationService { } - private String getPlaceholderValue(String placeholder, Dossier project, FileModel fileStatus, - Map fileAttributePlaceholders, - Map dossierAttributesPlaceholders) { + private String getPlaceholderValue(String placeholder, String dossierName, String filename, + PlaceholderModel placeholderModel) { if (placeholder.equals(FORMAT_DATE_ISO_PLACEHOLDER)) { return OffsetDateTime.now().format(FORMAT_DATE_ISO); @@ -318,23 +340,19 @@ public class ExcelTemplateReportGenerationService { return OffsetDateTime.now().format(FORMAT_TIME_ISO); } if (placeholder.equals(DOSSIER_NAME_PLACEHOLDER)) { - return project.getDossierName(); + return dossierName; } if (placeholder.equals(FILE_NAME_PLACEHOLDER)) { - return null; + return filename; + } + if (placeholderModel.getFileAttributeValueByPlaceholder().containsKey(placeholder)) { + return placeholderModel.getFileAttributeValueByPlaceholder().get(placeholder); } - if (fileAttributePlaceholders.containsKey(placeholder)) { - String id = fileAttributePlaceholders.get(placeholder); - if (fileStatus.getFileAttributes().containsKey(id)) { - return fileStatus.getFileAttributes().get(id); - } else { - return null; - } - } - if (dossierAttributesPlaceholders.containsKey(placeholder)) { - return dossierAttributesPlaceholders.get(placeholder); + if (placeholderModel.getDossierAttributesValueByPlaceholder().containsKey(placeholder)) { + return placeholderModel.getDossierAttributesValueByPlaceholder().get(placeholder); } + return null; } 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 index 12b8c38..c757ea0 100644 --- 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 @@ -11,9 +11,12 @@ import org.springframework.stereotype.Service; import com.iqser.red.service.redaction.report.v1.server.model.ReportRedactionEntry; +import io.micrometer.core.annotation.Timed; + @Service public class IuclidFunctionService { + @Timed("computeIuclidFunction") public String computeIuclidFunction(List reportRedactionEntries, String filename) { Map> entriesPerJustification = new HashMap<>(); diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ReportGenerationService.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ReportGenerationService.java index 992426e..ecd9aeb 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ReportGenerationService.java +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ReportGenerationService.java @@ -1,5 +1,18 @@ package com.iqser.red.service.redaction.report.v1.server.service; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.springframework.stereotype.Service; + import com.iqser.red.service.persistence.service.v1.api.model.annotations.AnnotationStatus; import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.ReportTemplate; import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.Dossier; @@ -22,30 +35,14 @@ import com.iqser.red.service.redaction.v1.model.RedactionLog; import com.iqser.red.service.redaction.v1.model.RedactionLogEntry; import com.iqser.red.storage.commons.exception.StorageObjectDoesNotExist; +import io.micrometer.core.annotation.Timed; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.xssf.streaming.SXSSFWorkbook; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; -import org.apache.poi.xwpf.usermodel.XWPFDocument; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - @Slf4j @Service @RequiredArgsConstructor -@SuppressWarnings("PMD") public class ReportGenerationService { private final ReportStorageService reportStorageService; @@ -55,12 +52,13 @@ public class ReportGenerationService { private final DossierClient dossierClient; private final ReportTemplateClient reportTemplateClient; private final RedactionLogClient redactionLogClient; - private final ExcelTemplateReportGenerationService excelTemplateReportGenerationService; + private final ExcelReportGenerationService excelTemplateReportGenerationService; private final GeneratePlaceholderService generatePlaceholderService; @SneakyThrows - public List generateReport(ReportRequestMessage reportMessage) { + @Timed("generateReports") + public List generateReports(ReportRequestMessage reportMessage) { List storedFileInformation = Collections.synchronizedList(new ArrayList<>()); @@ -78,7 +76,7 @@ public class ReportGenerationService { try (ByteArrayInputStream is = new ByteArrayInputStream(excelTemplate)) { XSSFWorkbook readWorkbook = new XSSFWorkbook(is); SXSSFWorkbook writeWorkbook = new SXSSFWorkbook(); - for(Sheet sheet: readWorkbook){ + for (Sheet sheet : readWorkbook) { writeWorkbook.createSheet(sheet.getSheetName()); } MultiFileWorkbook multiFileWorkbook = new MultiFileWorkbook(readWorkbook, writeWorkbook, templateId, reportTemplate.getFileName()); @@ -117,29 +115,27 @@ public class ReportGenerationService { generatePlaceholderService.resolveFileAttributeValues(fileStatus, placeholderModel); - List reportEntries = getReportEntries(reportMessage.getDossierId(), reportMessage.getFileIds() .get(j), fileStatus.isExcluded()); var isLastFile = j == reportMessage.getFileIds().size() - 1; for (MultiFileWorkbook multiFileWorkbook : multiFileWorkbooks) { - if(excelModel == null){ - excelModel = excelTemplateReportGenerationService.caculateExcelModel(multiFileWorkbook.getReadWorkBook().getSheetAt(0)); + if (excelModel == null) { + excelModel = excelTemplateReportGenerationService.calculateExcelModel(multiFileWorkbook.getReadWorkBook() + .getSheetAt(0)); } - excelModel = excelTemplateReportGenerationService.generateReport(reportEntries, placeholderModel, multiFileWorkbook.getTemplateName(), multiFileWorkbook.getWriteWorkbook(), fileStatus, excelModel); + excelTemplateReportGenerationService.generateExcelReport(reportEntries, placeholderModel, multiFileWorkbook.getTemplateName(), multiFileWorkbook.getWriteWorkbook(), dossier.getDossierName(), fileStatus, excelModel, isLastFile); } for (MultiFileDocument multiFileDocument : multiFileDocuments) { - wordReportGenerationService.generateReport(reportEntries, placeholderModel, multiFileDocument.getTemplateName(), multiFileDocument.getDocument(), fileStatus, dossier, isLastFile); - + wordReportGenerationService.generateWordReport(reportEntries, placeholderModel, multiFileDocument.getTemplateName(), multiFileDocument.getDocument(), fileStatus, dossier, isLastFile); } for (ReportTemplate reportTemplate : singleFilesTemplates) { storedFileInformation.add(createReportFromTemplate(dossier, fileStatus, placeholderModel, reportTemplate.getFileName(), reportMessage.getDownloadId(), reportEntries, reportTemplate)); - } long end = System.currentTimeMillis(); @@ -174,11 +170,11 @@ public class ReportGenerationService { try (ByteArrayInputStream is = new ByteArrayInputStream(excelTemplate)) { XSSFWorkbook readWorkbook = new XSSFWorkbook(is); SXSSFWorkbook writeWorkbook = new SXSSFWorkbook(); - for(Sheet sheet: readWorkbook){ + for (Sheet sheet : readWorkbook) { writeWorkbook.createSheet(sheet.getSheetName()); } - var excelModel = excelTemplateReportGenerationService.caculateExcelModel(readWorkbook.getSheetAt(0)); - excelTemplateReportGenerationService.generateReport(reportEntries, placeholderModel, templateName, writeWorkbook, fileStatus, excelModel); + var excelModel = excelTemplateReportGenerationService.calculateExcelModel(readWorkbook.getSheetAt(0)); + excelTemplateReportGenerationService.generateExcelReport(reportEntries, placeholderModel, templateName, writeWorkbook, dossier.getDossierName(), fileStatus, excelModel, true); byte[] template = excelTemplateReportGenerationService.toByteArray(writeWorkbook); String storageId = reportStorageService.storeObject(downloadId, template); return new StoredFileInformation(fileStatus.getId(), storageId, ReportType.EXCEL_TEMPLATE_SINGLE_FILE, reportTemplate.getTemplateId()); @@ -189,7 +185,7 @@ public class ReportGenerationService { byte[] wordTemplate = reportStorageService.getReportTemplate(reportTemplate.getStorageId()); try (ByteArrayInputStream is = new ByteArrayInputStream(wordTemplate)) { XWPFDocument doc = new XWPFDocument(is); - wordReportGenerationService.generateReport(reportEntries, placeholderModel, templateName, doc, fileStatus, dossier, true); + wordReportGenerationService.generateWordReport(reportEntries, placeholderModel, templateName, doc, fileStatus, dossier, true); byte[] template = wordReportGenerationService.toByteArray(doc); String storageId = reportStorageService.storeObject(downloadId, template); return new StoredFileInformation(fileStatus.getId(), storageId, ReportType.WORD_SINGLE_FILE, reportTemplate.getTemplateId()); @@ -200,6 +196,7 @@ public class ReportGenerationService { } + @Timed("getReportEntries") private List getReportEntries(String dossierId, String fileId, boolean isExcluded) { if (isExcluded) { @@ -228,7 +225,4 @@ public class ReportGenerationService { return redactionLogConverterService.convertAndSort(redactionLog, legalBasisMappings); } - - - } diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ReportMessageReceiver.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ReportMessageReceiver.java index 7fc04d0..fbfaf0d 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ReportMessageReceiver.java +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ReportMessageReceiver.java @@ -36,7 +36,7 @@ public class ReportMessageReceiver { long start = System.currentTimeMillis(); log.info("Start generating reports for downloadId {}", reportMessage.getDownloadId()); - List storedFileInformation = reportGenerationService.generateReport(reportMessage); + List storedFileInformation = reportGenerationService.generateReports(reportMessage); addToReportResultQueue(reportMessage.getUserId(), reportMessage.getDownloadId(), storedFileInformation, 1); long end = System.currentTimeMillis(); 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 7b962f0..cd439e9 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 @@ -34,16 +34,15 @@ import static com.iqser.red.service.redaction.report.v1.server.service.Placehold @Slf4j @Service @RequiredArgsConstructor -@SuppressWarnings("PMD") public class WordReportGenerationService { private final IuclidFunctionService iuclidFunctionService; @Timed("generateWordReport") - public XWPFDocument generateReport(List reportEntries, PlaceholderModel placeholderModel, - String reportTemplateName, XWPFDocument doc, FileModel fileModel, - Dossier dossier, boolean isLastFile) { + public XWPFDocument generateWordReport(List reportEntries, PlaceholderModel placeholderModel, + String reportTemplateName, XWPFDocument doc, FileModel fileModel, + Dossier dossier, boolean isLastFile) { long start = System.currentTimeMillis(); @@ -53,26 +52,19 @@ public class WordReportGenerationService { replaceImagePlaceholders(doc, imagePlaceholder); } - long t1 = System.currentTimeMillis(); XWPFTable table = getRedactionTable(doc); - var placeholderFunctions = computePlaceholderPos(table, placeholderModel); - addTableRows(table, reportEntries, fileModel.getFilename(), placeholderModel, placeholderFunctions); - long t2 = System.currentTimeMillis(); - log.debug("Table time: {}", (t2 - t1)); - t1 = System.currentTimeMillis(); + var placeholderFunctions = computePlaceholderPos(table, placeholderModel); + + addRedactionEntryRows(table, reportEntries, fileModel.getFilename(), placeholderModel, placeholderFunctions); + replaceTextPlaceholders(doc, placeholderModel, dossier.getDossierName(), fileModel.getFilename(), table, reportEntries); if (isLastFile) { removePlaceholdersRow(table); } - t2 = System.currentTimeMillis(); - log.debug("Text time: {}", (t2 - t1)); - - long end = System.currentTimeMillis(); - - log.info("Report Generation took: {} for file with id {}, pageCount: {}, redactionLogEntryCount: {}, reportName: {}, className: {}", end - start, fileModel.getId(), fileModel.getNumberOfPages(), reportEntries.size(), reportTemplateName, getClass().getSimpleName()); + log.info("Report Generation took: {} for file with id {}, pageCount: {}, redactionLogEntryCount: {}, reportName: {}, className: {}", System.currentTimeMillis() - start, fileModel.getId(), fileModel.getNumberOfPages(), reportEntries.size(), reportTemplateName, getClass().getSimpleName()); return doc; } catch (Exception e) { @@ -175,6 +167,7 @@ public class WordReportGenerationService { } + @Timed("word-replaceTextPlaceholders") public void replaceTextPlaceholders(XWPFDocument doc, PlaceholderModel placeholderModel, String dossierName, String fileName, XWPFTable tableToSkip, List reportRedactionEntries) { @@ -267,6 +260,7 @@ public class WordReportGenerationService { } + @Timed("word-computePlaceholderPos") private PlaceHolderFunctions computePlaceholderPos(XWPFTable table, PlaceholderModel placeholderModel) { Set foundPlaceHolder = new HashSet<>(); @@ -288,23 +282,25 @@ public class WordReportGenerationService { } - private void addTableRows(XWPFTable table, List reportEntries, String filename, - PlaceholderModel placeholderModel, PlaceHolderFunctions placeHolderFunctions) { + @Timed("word-addRedactionEntryRows") + private void addRedactionEntryRows(XWPFTable table, List reportEntries, String filename, + PlaceholderModel placeholderModel, PlaceHolderFunctions placeHolderFunctions) { if (table == null) { return; } - if (placeHolderFunctions.getFoundPlaceHolder().contains(SEEDS_FUNCTION_REDACTION_GROUPED_BY_JUSTIFICATION_PAGES_PLACEHOLDER)) { + if (placeHolderFunctions.getFoundPlaceHolder() + .contains(SEEDS_FUNCTION_REDACTION_GROUPED_BY_JUSTIFICATION_PAGES_PLACEHOLDER)) { var redactionsPerJustification = getRedactionsPerJustification(reportEntries); - for (Map.Entry> entry : redactionsPerJustification.entrySet()) { XWPFTableRow row = table.createRow(); for (Map.Entry> entry1 : placeHolderFunctions.getFunctionPerPlaceHolder() .entrySet()) { - setText(row.getCell(entry1.getKey()), entry1.getValue().apply(new PlaceholderInput(filename, null, placeholderModel, entry))); + setText(row.getCell(entry1.getKey()), entry1.getValue() + .apply(new PlaceholderInput(filename, null, placeholderModel, entry))); } } } else { @@ -312,7 +308,8 @@ public class WordReportGenerationService { XWPFTableRow row = table.createRow(); for (Map.Entry> entry1 : placeHolderFunctions.getFunctionPerPlaceHolder() .entrySet()) { - setText(row.getCell(entry1.getKey()), entry1.getValue().apply(new PlaceholderInput(filename, entry, placeholderModel, null))); + setText(row.getCell(entry1.getKey()), entry1.getValue() + .apply(new PlaceholderInput(filename, entry, placeholderModel, null))); } }); } 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 8002e82..37cb5ba 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 @@ -3,13 +3,7 @@ package com.iqser.red.service.redaction.report.v1.server; import com.amazonaws.services.s3.AmazonS3; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.DossierAttributeConfig; -import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.ReportTemplate; import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.Dossier; -import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.DossierAttribute; -import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.DossierAttributeType; -import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileAttributeConfig; -import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileAttributeType; import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileModel; import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.legalbasis.LegalBasis; import com.iqser.red.service.redaction.report.v1.server.client.DossierAttributesClient; @@ -20,7 +14,7 @@ import com.iqser.red.service.redaction.report.v1.server.configuration.MessagingC import com.iqser.red.service.redaction.report.v1.server.model.ImagePlaceholder; import com.iqser.red.service.redaction.report.v1.server.model.PlaceholderModel; import com.iqser.red.service.redaction.report.v1.server.model.ReportRedactionEntry; -import com.iqser.red.service.redaction.report.v1.server.service.ExcelTemplateReportGenerationService; +import com.iqser.red.service.redaction.report.v1.server.service.ExcelReportGenerationService; import com.iqser.red.service.redaction.report.v1.server.service.GeneratePlaceholderService; import com.iqser.red.service.redaction.report.v1.server.service.RedactionLogConverterService; import com.iqser.red.service.redaction.report.v1.server.service.WordReportGenerationService; @@ -32,7 +26,6 @@ import org.apache.commons.io.IOUtils; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xwpf.usermodel.XWPFDocument; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -42,8 +35,6 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.test.context.junit4.SpringRunner; import java.io.FileOutputStream; -import java.io.IOException; -import java.time.OffsetDateTime; import java.util.*; import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.DOSSIER_NAME_PLACEHOLDER; @@ -56,7 +47,6 @@ import static com.iqser.red.service.redaction.report.v1.server.service.Placehold import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.SEEDS_FUNCTION_JUSTIFICATION_PLACEHOLDER; import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.SEEDS_FUNCTION_REDACTION_GROUPED_BY_JUSTIFICATION_PAGES_PLACEHOLDER; import static com.iqser.red.service.redaction.report.v1.server.utils.OsUtils.getTemporaryDirectory; -import static org.mockito.Mockito.when; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @RunWith(SpringRunner.class) @@ -97,7 +87,7 @@ public class RedactionReportIntegrationTest { private DossierAttributesClient dossierAttributesClient; @Autowired - private ExcelTemplateReportGenerationService excelTemplateReportGenerationService; + private ExcelReportGenerationService excelTemplateReportGenerationService; @Autowired private GeneratePlaceholderService generatePlaceholderService; @@ -127,7 +117,7 @@ public class RedactionReportIntegrationTest { List.of(new ImagePlaceholder("{{dossier.attribute.Signature}}",IOUtils.toByteArray(imageResource.getInputStream())))); XWPFDocument doc = new XWPFDocument(wordTemplateResource.getInputStream()); - wordReportGenerationService.generateReport(reportEntries, placeholders, "test", doc, fileModel, dossier, true); + wordReportGenerationService.generateWordReport(reportEntries, placeholders, "test", doc, fileModel, dossier, true); byte[] wordReport = wordReportGenerationService.toByteArray(doc); try (FileOutputStream fileOutputStream = new FileOutputStream(getTemporaryDirectory() + "/Justification Appendix A1_justification.docx")) { fileOutputStream.write(wordReport); @@ -159,7 +149,7 @@ public class RedactionReportIntegrationTest { List.of(new ImagePlaceholder("{{dossier.attribute.Signature}}",IOUtils.toByteArray(imageResource.getInputStream())))); XWPFDocument doc = new XWPFDocument(wordTemplateResource.getInputStream()); - wordReportGenerationService.generateReport(reportEntries, placeholders, "test", doc, fileModel, dossier, true); + wordReportGenerationService.generateWordReport(reportEntries, placeholders, "test", doc, fileModel, dossier, true); byte[] wordReport = wordReportGenerationService.toByteArray(doc); try (FileOutputStream fileOutputStream = new FileOutputStream(getTemporaryDirectory() + "/Justification Appendix A2_justification.docx")) { fileOutputStream.write(wordReport); @@ -183,7 +173,7 @@ public class RedactionReportIntegrationTest { var placeholders = buildPlaceHolderModel(new HashMap<>(), Map.of("{{file.attribute.Path}}", "Path"), List.of()); - wordReportGenerationService.generateReport(reportEntries, placeholders, "test", doc, fileStatus, dossier, true); + wordReportGenerationService.generateWordReport(reportEntries, placeholders, "test", doc, fileStatus, dossier, true); byte[] report = wordReportGenerationService.toByteArray(doc); try (FileOutputStream fileOutputStream = new FileOutputStream(getTemporaryDirectory() + "/IUCLID_Template_justification.docx")) { @@ -208,7 +198,7 @@ public class RedactionReportIntegrationTest { var placeholders = buildPlaceHolderModel(new HashMap<>(), Map.of("{{file.attribute.Path}}", "Path"), List.of()); - wordReportGenerationService.generateReport(reportEntries, placeholders, "test", doc, fileStatus, dossier, true); + wordReportGenerationService.generateWordReport(reportEntries, placeholders, "test", doc, fileStatus, dossier, true); byte[] report = wordReportGenerationService.toByteArray(doc); try (FileOutputStream fileOutputStream = new FileOutputStream(getTemporaryDirectory() + "/seeds-report.docx")) { @@ -238,8 +228,8 @@ public class RedactionReportIntegrationTest { var placeholders = buildPlaceHolderModel(new HashMap<>(), Map.of("{{file.attribute.Path}}", "Path"), List.of()); - wordReportGenerationService.generateReport(reportEntries, placeholders, "test", doc, fileStatus, dossier, false); - wordReportGenerationService.generateReport(reportEntriesSecondFile, placeholders, "test", doc, fileModelSecondFile, dossier, true); + wordReportGenerationService.generateWordReport(reportEntries, placeholders, "test", doc, fileStatus, dossier, false); + wordReportGenerationService.generateWordReport(reportEntriesSecondFile, placeholders, "test", doc, fileModelSecondFile, dossier, true); byte[] report = wordReportGenerationService.toByteArray(doc); try (FileOutputStream fileOutputStream = new FileOutputStream(getTemporaryDirectory() + "/seeds-report-multifile.docx")) { @@ -274,7 +264,7 @@ public class RedactionReportIntegrationTest { List.of(new ImagePlaceholder("{{dossier.attribute.Signature}}",IOUtils.toByteArray(imageResource.getInputStream())))); XWPFDocument doc = new XWPFDocument(wordTemplateResource.getInputStream()); - wordReportGenerationService.generateReport(reportEntries, placeholders, "test", doc, fileModel, dossier, true); + wordReportGenerationService.generateWordReport(reportEntries, placeholders, "test", doc, fileModel, dossier, true); byte[] wordReport = wordReportGenerationService.toByteArray(doc); try (FileOutputStream fileOutputStream = new FileOutputStream(getTemporaryDirectory() + "/6464 appendix_b EFSA dRAR_justification.docx")) { fileOutputStream.write(wordReport); @@ -297,10 +287,10 @@ public class RedactionReportIntegrationTest { var placeholders = buildPlaceHolderModel(new HashMap<>(), new HashMap<>(), List.of()); XSSFWorkbook readWorkbook = new XSSFWorkbook(templateResource.getInputStream()); - var excelModel = excelTemplateReportGenerationService.caculateExcelModel(readWorkbook.getSheetAt(0)); + var excelModel = excelTemplateReportGenerationService.calculateExcelModel(readWorkbook.getSheetAt(0)); SXSSFWorkbook writeWorkbook = new SXSSFWorkbook(); writeWorkbook.createSheet("Sheet1"); - excelTemplateReportGenerationService.generateReport(reportEntries, placeholders, "test", writeWorkbook, fileModel, excelModel); + excelTemplateReportGenerationService.generateExcelReport(reportEntries, placeholders, "test", writeWorkbook, "dossierName" ,fileModel, excelModel, true); byte[] excelTemplateReport3 = excelTemplateReportGenerationService.toByteArray(writeWorkbook); @@ -326,15 +316,15 @@ public class RedactionReportIntegrationTest { var placeholders = buildPlaceHolderModel(new HashMap<>(), new HashMap<>(), List.of()); XSSFWorkbook readWorkbook = new XSSFWorkbook(templateResource.getInputStream()); - var excelModel = excelTemplateReportGenerationService.caculateExcelModel(readWorkbook.getSheetAt(0)); + var excelModel = excelTemplateReportGenerationService.calculateExcelModel(readWorkbook.getSheetAt(0)); SXSSFWorkbook writeWorkbook = new SXSSFWorkbook(); writeWorkbook.createSheet("Sheet1"); - excelModel = excelTemplateReportGenerationService.generateReport(reportEntries, placeholders, "test", writeWorkbook, fileModel, excelModel); + excelTemplateReportGenerationService.generateExcelReport(reportEntries, placeholders, "test", writeWorkbook, "dossierName" ,fileModel, excelModel, false); RedactionLog redactionLogSecondFile = objectMapper.readValue(new ClassPathResource("files/excelReportRedactionLog.json").getInputStream(), RedactionLog.class); List reportEntriesSecondFile = redactionLogConverterService.convertAndSort(redactionLogSecondFile, legalBasisMapping); FileModel fileModelSecondFile = FileModel.builder().filename("secondFile").build(); - excelTemplateReportGenerationService.generateReport(reportEntriesSecondFile, placeholders, "test", writeWorkbook, fileModelSecondFile, excelModel); + excelTemplateReportGenerationService.generateExcelReport(reportEntriesSecondFile, placeholders, "test", writeWorkbook,"dossierName" , fileModelSecondFile, excelModel, true); byte[] excelTemplateReport3 = excelTemplateReportGenerationService.toByteArray(writeWorkbook); @@ -344,6 +334,37 @@ public class RedactionReportIntegrationTest { } + @Test + @SneakyThrows + public void testExcelPlaceholders() { + + RedactionLog redactionLog = objectMapper.readValue(new ClassPathResource("files/redactionLog.json").getInputStream(), RedactionLog.class); + List legalBasisMapping = objectMapper.readValue(new ClassPathResource("files/legalBasisMapping.json").getInputStream(), new TypeReference<>() {}); + List reportEntries = redactionLogConverterService.convertAndSort(redactionLog, legalBasisMapping); + var imageResource = new ClassPathResource("files/exampleImage.jpg"); + + FileModel fileModel = FileModel.builder().filename("filename").build(); + + ClassPathResource templateResource = new ClassPathResource("templates/Excel Report_PlaceholderTest.xlsx"); + + var placeholders = buildPlaceHolderModel(Map.of("{{dossier_attribute.test1}}", "replaced_dossier_test1"), Map.of("{{file_attribute.test1}}", "replaced_file_test1", "{{file_attribute.test2}}", "replaced_file_test2"), List.of(new ImagePlaceholder("{{dossier.attribute.Signature}}",IOUtils.toByteArray(imageResource.getInputStream())))); + + XSSFWorkbook readWorkbook = new XSSFWorkbook(templateResource.getInputStream()); + var excelModel = excelTemplateReportGenerationService.calculateExcelModel(readWorkbook.getSheetAt(0)); + SXSSFWorkbook writeWorkbook = new SXSSFWorkbook(); + writeWorkbook.createSheet("Sheet1"); + excelTemplateReportGenerationService.generateExcelReport(reportEntries, placeholders, "test", writeWorkbook,"dossierName" , fileModel, excelModel, true); + + + byte[] excelTemplateReport3 = excelTemplateReportGenerationService.toByteArray(writeWorkbook); + try (FileOutputStream fileOutputStream = new FileOutputStream(getTemporaryDirectory() + "/PlaceholderTest_justification.xlsx")) { + fileOutputStream.write(excelTemplateReport3); + } + } + + + + private PlaceholderModel buildPlaceHolderModel(Map dossierAttributes, Map fileAttributes, List imagePlaceholders){ var defaultPlaceHolder = new HashSet(Set.of(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, SEEDS_FUNCTION_REDACTION_GROUPED_BY_JUSTIFICATION_PAGES_PLACEHOLDER, SEEDS_FUNCTION_JUSTIFICATION_PLACEHOLDER)); diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/templates/Excel Report_PlaceholderTest.xlsx b/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/templates/Excel Report_PlaceholderTest.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..dc965aafb77a6046bf96b7493b149c222ebe2498 GIT binary patch literal 9671 zcmeHN1y>x|)@|Iiad)>M!JXh9ER6-1;1FDr#)3mba0^bb1owsn4FQ4$x8Uw>UuWjc znwiXezu>*N=s9UW-$5aC&J0r0T<|9AWs&p>(7fcj@1+$R@`x3a68%5&9H zr~-T7UVJt!iH@$sp7$1p*>-jh*%3FmGVciO1wY~s&-w8l4cb;aIo5;U^t5PWM!oLq z&@~|!WrPxwLfk;l4sr>|r5=%*n#NdX08(8WK5O&KElVqxli3myNxWLgn(5WU zTkL7Vd=DxS?wjpgQrBC+6Z%p*hPyHX9JjY)^)oqH;Z#D5qVt<0@-!*bq&0ZTYOdYn zcAcWq?#|N4H}Tv8et{<}z72bRXc4)kU64wRpRBqx^7K8ZC;kOH)ZfV87s?VFb*g#Y z{FuNPERbPAyPBC3|Cg4by{pi7c4Uv=Q{6V3b$<1&zeuf z)Nl9Onqlz%QSw$0?>#aA@bG{D(EJ-M>vVbOPhnV7g|Q9;MoUvSD+hOOuAk@s(eb~S zgMWGTa)_G7XCBP31I63$zKe}MG5HMS^H63ATphAt1cjis zz}qjg^I|dEeYEF4_{*d42}J4Zy(+>|u3cZEurawM%e$5@cj0+XoK2jkDJc7}c(%o{ zl{J>UQ|Vu%mmfQl`-nTtrB8+iETjk{5>I;#>QQ^PV18K%HzD(MuOjS2gJAA<;&4XL zWOC6KmgE~@)!orF!ajF%o9RmbJ_p*fD>B`uw!(HF%(LA@=zL5q9Xd|rGN8aae@^wx zK5aUF{7c?pgqxaoFv zcW_YqYguQRX!!z97s=V0jSar5a3eT}`5X-PdR_=%|3Cnqb+VU4;v@P39S%g1oCi+^ z5m3$dDyYB;NI?AE?VYkc4D57wV3P!-k0YBvX#J9fku*HNh)(XbX*7)Aeu(EKuf6*> z$}cd?4Aic^x(<1|TLG?ZXrM`FwO)H@lAS$A&H?*SkDQRADT!hE07f>)Y1uD3pPZu} zx87Q-`3$}2%)iE5qbO6MdM_aEALc<5-lsqv#0qg=%S&s~Jy`aTx%t8hojv9lI6bgl z?eCpfgnj9MLdL~{j)fBe01!n10B~S;z##KiuoUQOInQzs1mx5{JU`kurXFp{b3B5f3eV#VENv?v^NK09RGndzlSb1$ADnM!)eqyLjxF>m{_1(E@ zgVAfN3N=|u%t7SY?=GO}boWU`3I(k&ay9}oT(=W4%r+B@o{jXESo%(YM@HHxr;eN; z(L8}&0T(O*9Ak3c`C7n7eC@_wDgq>|0q4ic%!07Qk2gPIq^Lsoz?N-W;bu~uH1U!Fzm)^>N?&fX zY#0J{{MAIa_NDvi#e}S<`PJK)Q7xD#1tUdF?AN5i!yE5)-Nikhz0mY_>4m=hH%(<aF z8wMZj){x!tOa9aFyxkM6=%#7fMdFBf(Sulw)V z){E%w>SL*S*+_LV4q7c%WBd;!3??ta(;;N*>gpJ*dSN)d_Ca+tC8%%-nKrI^CYG)w z!+i`4mI+~#i40gNQESIw)d3%2V+m=2vl3yN6a=&#Oe1eE|GOhCVQ5NV;NwFiZrr6l z%(#n%iJ!drC$j`MHCTbLA|e=e8S8guxqJ9HSh@cU!d?1~&iO*P_kp8#C|zEWucc%O ztL-6ldv^LFrQ&u_k>~h3n6c4M6GZ2Oprkb1YI|+Q+!|fs;FjbG>kbjZHho# z8DFuS-t2rRif43woOaXubYIQ5c%qiIG#+bU4&lYT#))ZATHQ6x)JpQBJ%HSp^}gQD zeAYojck)DSTY~6!Kr!Tf5m}M_N`q*(?R1_d=WTp^R~4)C9Ncmx*8;ru)3c4h4Z`XL zt-(nnmw?TWM#WtF63%3$o1j;v#ysyr$B-h>v(zcyo{sDL+MBRUe^FipRC1R^67SYE zmF8U63em$I_r`;#n3pjYMbD%QBC4kNdQY29o-vZoi-gVziny;N@Z{752@mT?`9|yv z+hA2p^FZ}8(HV4W*MIQf$}$ibz6UAp(S#9De?-BWd=*NdEo@&iG$*F9U))?2arj+R>ZARl_s*mac|ON{TH@u%X5HQi4aJ#7G~cPWrGVt#%MqCq8Ri)3&ht^7%k%o9|xs z4ExA>srdK4N4;3$YB%ovJ0h_A&VmHaeLoa^#f!;f6f4Ti3!Fu-cOM;R3a+y? zVpM_{I?RVEL5uMKMh14T8ay*qrSRpqBv*$zQxU%IlsKOOE^3Sr6y|F z+NQ;6V|}LDrqpH59Yf5{oy|d9rkf3k=Xhvpe00|lPzqh@G-FPktAlsawV%uE9Xvl$ z4W54xBdwpkCqTD0oyiVEYl=hov4)4%sw7jqz#n1G9T)8KR+R|HmCAIAD(KMU$t?|u zldY}4f3eRXqI0ZG)Jnt;2zoerYG5zgLn_*XzhCwP#=k`m0~En6by!aBp!q37{FawJ zY^@xvxPLo8`WZuZpAANl3lczyuO%_vy>10o;usf}h8+^;87#AsDC+8WweJjF_Df)^vh71Q~kSA7@!hKtD@kMr^?}^9J zXj`hULMp~5NU~|W=COEY^i(RtSaQ${Ar^jr4aKBrJS#M(D-4cXmD!v?H$2CF)ZQtC zD>;XE$<#{1vus4&s9_0>9o9mx7n!1a6+hAE5cN_}1}~%%u@`DrkkCUeO~B@wegV9+ z^t;a76X&d(>IIqz^j|LCYPZHxQYRD2aLA>&9iWaR?3nSxJ&7`f?xR@nVqPCqnC&*O)@^ien%#?CQ@m7FnL|~Rsu^ZAFrIh zuKWA5Dvw~y7q3REu;<%+t3dUKVfWkVgL|3*9IeQO*TDj}&%YsYgS@enkCw?KcKJoc z4WY<|mosGTCiWNL=0ek8I%UXb0T0%4-IjrgN*bWNSLUHL3Fh`&Ri9X}gMn2_y7?#k zCe}%FkCY=g06{#R621nPwKrHk9%?Mt568T~l_nqW&6mUj_qkBnPV@bzpfj{nK}xOK z&;;6wU3itQnfU5nAicVE^_nSMMLV3&(eCs~xJHiT?cPO4ysfv5kbs!zcy^zetQ=;SsG5bQR#iOmzbiF(|WmICK=?r`)y%; zvm*pYDLT{(i`q}{8779uR2%RD)P8OpgVUcd3JVWEvE0ti99ElC3yC#y_tG*)YLv`@Yr{|Z z;uONk=d78e$lr~YoP8ZbX})VPPbtFz-A}-?0jm{wg?m%VWpa^15ZDvk-cjPq8WC|J z(2gfNpF8q^XHqj}g!2-PU9iO}{`ClJbTdFZSz z3SaNX!es6jvB}aG*wBr6fL7T*-dj5Xt+yr%S6LZ3n^G4f_YV-4I2?1u?O%0~8%h9W zw%Uy}TiHD(6*|ZVy#c|^l!bnfGNL68aMiQm{!ePcqr6l$$L@6bcc_E80y^IZhf4c& zG(rU};6>zp4Ft80(B_*D_~S07g!Vj=2i}*ZHEYtO#ie|j5b`LsjI5kE*`HT#+YeWN znex)^6+}D+ug9_;Pd6OzY>h^4`KA2k+pGvewQxeL!a&mZRs{N$fH6I-*ZNL4CgX;I zBn6a~wms555_+!68T|vB@0T*|3Iv4{(@6o0=e!8Vih^$|$Ww~-Qi^5_jTASVP~a0^QDcDnkZo=Wa$Wr$lqz z1A)?!p=Z@@OR1=hLM+7#6u?F_L%e6%LCn}i(Vl~4ELpN!LU}c)R?Lw~>#RnJn_npI zYL8zZB~_48H1B^3E){vccrzYwqE#Vg6K@@84Jyx5R})MNY?NL2z$PijS*E@H{a~DK zqj?>&L!*^d+(oWF-ynpGIdSUER^))yO4&KNCGhZ#qSL~S0eU&?Bmoskz zT3ZX7c+6cBw%D)o9vhiXr?AJe`#G05tp~&bw&tpIkq7N-p0e|o?Mc&az9b8^Zuri6 zYCrGm{B0*JW_{`P0i~VGw@S}o5LBz=ZOgojCOhj1`Jjw=iO!64+x#ZV9?COP1a^%r z`V*u^{n@&^e_y;KoF`BNV2gKkbO3)#2Vb(2r@LC0}oZ zsywl12rUYTFU_e+yQ8Ka=pRHfsJJRtg;A-BCF}#Iu&;Np$cfYAWQ?>#&oIu#{PtoH ztj)~!ZLYiMHJO*Pg}}w7t>Cx-kvENXPwIBGAmt*(|j>30{KqaIokab>ZcD~7sz%e(-+nMd%OI-OA{B1=TAftaJc#T96|DbC^R6OYmb-f%;cAI@ga?9C$yB@^SdlJBW15>{Lf( zakw1|Zb{Hbms3Jcz8&1rms!z%{Ipj}hhe2IQQb|Sf!7QQL%wERUgBC$*x0erq!D|L zFP|sbvuV>gJM$>aSX?Z~PE`A4nImyk%yfMtGXYcT6q0I2DhBt33l93jy-+vtveyUj z(^_d1>Xeg(G1<+Vs>dLAlnePgNN5!gs|aiEs^}Uw(KE4uQ3i`G%c(nS>>Oz)wz%|i zHRfoU$FT{?dl`{fU*c$~)HglI@{M*g1)GPzqiNum+r~fNWtg`5p`T>Xo=buA#e>Ku zcuhZPlZEBc`QwcSR!I-#=qxv|mseI~Skx5^H4hIkTs%DZCu(7Xd2r>bWPAYw@R8{< z@Oj=?GHkg1fi*i)+ke0CZ9=}OVj>+{yM4b}BvH)xg5w?i^@nz>>da(h9d7Vkv5;n#+z=14LY2#LYC zH|3_)&IcRop0sFzad~nkS)~iXuIocW2ua%F&d?o^>zcH8l7xJ>RQnrBTW zwiKC+>-oE@0)S4QP>@kY?Xe7)=pzuxCk0pPbg5OR_v5U%|H8Fck*Wxj%EFFtS zv1Y+6pUHbaw0EQp&Mf)5DLzH<^POFRqV0q>Dbkp>b3?z*lI4_-uZq<4X0786k3R8j z6Dvg`dDomaniPQD&(WS%EGb}6U;brJxKP^govQ!Lqn#uY!s$N4yz>R`9W7-Mb8HnJ zju7<<{1ytas7OYnF|S6tEf2n_?*T28i9nt~m%!EKz=tK)@G>Ffli0jd-)hqK07xag zc7m&}IP&R_Pxbfb5+2^~h(v38f_>J%HxFo6l$Gn?u3A#{7!%z!_vTi8eL#Gx7|A5` zn07yd{W^-2(qN$(Su5(_qXwV;2JH!I7h9&UC{HosN=nGhA2 zN{t%=Q>jq~ynfWehr}mRv7NmVqB&~JW1MD?kDzSbLDxBhuDpp<$XM?f5nZ{mE}V$E zs3d%GYMD8>%Ar&!W!N}6C6XA2Q&z*y`ld7an@CjJtrR5c_EZk|@l6n?RkhiE)!V$h zPHYxe@tG2xA+L_3trt()6j(~7S1WZ-_}g?SN!Xt(gV*}n;^_=dh+LY;GY5K zVtVn>W04Z6?B|^7ei)TWQ@VSXh@TGD?g`*#d?DcJq5|m`J;9*dPu!<_aH*$9XLOp^ z|IxmIHuFl$v|j3TaNcYFmP?>+)%Oj#;fKh!#CMme2F-o)513yUS`m9f61+V*Hgejp z5Jj$}7c*wyQ67p*BNg5T!Dj5A5=u`SP&WZKpU1GPcrZQN(%C}O&Dq7B+rrt+>OY;> z|H;@ep!sWes;|IgZ2BXdgoDzoTBPsF!Clpv^&o~wB3Hiif>bq!fo$#T3n6v13L@8& zuYRx^&-(p9L*<}JCyL`Bm;flx%QMR6_#MLy+T72rNTf-^2zv2c+7q|WD!^A;skiXV zWv2omj!cH=t?JS!XCGhQN)qxFW)^B<9}k{G`2k|jXjLijIL82ilTCkK|T*o@uK%2RriIjPZkHJgGbMPlL>%vN%=Eivh-8fmY=! z0M@Eb;t~#~j__8Ac<5NpWEr`3N;!v{XLd~#;Z3IvV@`71-MGTTja-;~L&J5Od0LH+ zNwu!l;Z^kT5H^7%qIO>e&yJc{AxJv#jy+VoepIek9|VO->Hoy>YHJcxF$~Azu&cO# z#j%--%l}{u!|#8N%%{>$vpl%1#GBNJ3l?AOUJxL6(l7l#1&`7mekr(rH+SI=?PJ%Q!uV;arCJCG~FoTy0x?s`I8ND5kFg%FS zlVSy4mFT*R)ai(UN0W_qBe_vRy> zu>BB3l1D|-9t&7F`WMhRf5sN*&bm5FL=9ew z5TLaX4tj2~6Xzz3ukEVz6+STo(dfEdfHZn>77Ot@Zf|YweAA3qe7e*uyl3h3d(&sv z6aPGwGv}H1E@sn~8~;v8JQM8h`rub;%69}ub~IGlzMYl{E!ewM8PLq2*Z$WVTh8$V z6M46qMTSGx;2fym75N|FhJ)vXCAfdzGV{mB{A2u=4K$i6e|PZr=KddsKgSdpWB$_W z|JCsC4bQ(Cw!(H1{QvFGU;X^r1^d&}0c=;$FFmnejeiyT|1?&@{LT1J+5cD5Usb+8 zO~Y~jRrCAR!LN17pAL{=Wy^ma{2x`zuU>v#xBTfvjNObuPfM`kp q;2(MWSM$FI$iJHF(EP>xKjBhS1qt?b0RVK^?-gum@QD8BzyAXcm+8s? literal 0 HcmV?d00001 diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/templates/TestReport.xlsx b/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/templates/TestReport.xlsx deleted file mode 100644 index 09e80a4b4d6575e00839f662f3261ac7be2a3715..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4966 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