diff --git a/redaction-report-service-v1/redaction-report-service-api-v1/src/main/java/com/iqser/red/service/redaction/report/v1/api/resource/ReportTemplateResource.java b/redaction-report-service-v1/redaction-report-service-api-v1/src/main/java/com/iqser/red/service/redaction/report/v1/api/resource/ReportTemplateResource.java index d4c8edf..02d3cd8 100644 --- a/redaction-report-service-v1/redaction-report-service-api-v1/src/main/java/com/iqser/red/service/redaction/report/v1/api/resource/ReportTemplateResource.java +++ b/redaction-report-service-v1/redaction-report-service-api-v1/src/main/java/com/iqser/red/service/redaction/report/v1/api/resource/ReportTemplateResource.java @@ -14,6 +14,12 @@ public interface ReportTemplateResource { String REPORT_TEMPLATE_PATH = "/report-templates"; + String REPORT_TEMPLATE_UPLOAD_PATH = "/upload-template"; + + String TEMPLATE_ID = "templateId"; + + String TEMPLATE_ID_PATH_VARIABLE = "/template-id/{" + TEMPLATE_ID + "}"; + String DOSSIER_TEMPLATE_ID = "dossierTemplateId"; String DOSSIER_TEMPLATE_ID_PATH_VARIABLE = "/dossier-template-id/{" + DOSSIER_TEMPLATE_ID + "}"; @@ -22,4 +28,8 @@ public interface ReportTemplateResource { @PostMapping(value = REPORT_TEMPLATE_PATH + DOSSIER_TEMPLATE_ID_PATH_VARIABLE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) List getReportTemplatesByPlaceholder(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody JSONPrimitive placeholder); + + @PostMapping(value = REPORT_TEMPLATE_UPLOAD_PATH + TEMPLATE_ID_PATH_VARIABLE) + void uploadTemplate(@PathVariable(TEMPLATE_ID) String templateId); + } diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/build.gradle.kts b/redaction-report-service-v1/redaction-report-service-server-v1/build.gradle.kts index cd58dda..1b5c27b 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/build.gradle.kts +++ b/redaction-report-service-v1/redaction-report-service-server-v1/build.gradle.kts @@ -47,6 +47,8 @@ dependencies { implementation("org.springframework.cloud:spring-cloud-starter-openfeign:4.0.4") implementation("org.apache.commons:commons-lang3:3.12.0") + implementation("com.github.ben-manes.caffeine:caffeine:3.1.8") + implementation("net.logstash.logback:logstash-logback-encoder:7.4") implementation("org.springframework.boot:spring-boot-starter-logging") implementation("ch.qos.logback:logback-classic") diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/controller/ReportTemplateController.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/controller/ReportTemplateController.java index e4d4e86..4477906 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/controller/ReportTemplateController.java +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/controller/ReportTemplateController.java @@ -10,6 +10,7 @@ import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSON import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.ReportTemplate; import com.iqser.red.service.redaction.report.v1.api.resource.ReportTemplateResource; import com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService; +import com.iqser.red.service.redaction.report.v1.server.utils.TemplateCache; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -28,4 +29,16 @@ public class ReportTemplateController implements ReportTemplateResource { return placeholderService.getReportTemplatesByPlaceholder(dossierTemplateId, placeholder.getValue()); } + + /** + * If a template with the same id is uploaded we evict it from the cache. + * + * @param templateId The id for the uploaded template + */ + @Override + public void uploadTemplate(String templateId) { + + TemplateCache.evictCache(templateId); + } + } diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ReportTemplatesModel.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ReportTemplatesModel.java new file mode 100644 index 0000000..fa0c260 --- /dev/null +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/model/ReportTemplatesModel.java @@ -0,0 +1,19 @@ +package com.iqser.red.service.redaction.report.v1.server.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.ReportTemplate; + +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; + +@FieldDefaults(makeFinal = true, level = AccessLevel.PUBLIC) +public final class ReportTemplatesModel { + + List singleFilesTemplates = Collections.synchronizedList(new ArrayList<>()); + List multiFileWorkbookReportTemplates = Collections.synchronizedList(new ArrayList<>()); + List multiFileDocumentReportTemplates = Collections.synchronizedList(new ArrayList<>()); + +} diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelReportGenerationService.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 index 2202307..dad541a 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelReportGenerationService.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 @@ -32,7 +32,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.SKIPPED_PLACEHOLDER; import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.time.OffsetDateTime; @@ -71,6 +70,7 @@ 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 com.iqser.red.service.redaction.report.v1.server.utils.ImageCache; import com.iqser.red.service.redaction.report.v1.server.utils.PixelUtil; import io.micrometer.core.annotation.Timed; @@ -106,6 +106,7 @@ public class ExcelReportGenerationService { private final ScmReportService componentReportService; private final FileAttributesConfigClient fileAttributesConfigClient; private final ComponentRowsReportService componentRowsReportService; + private CreationHelper creationHelper; @Timed("redactmanager_generateExcelReport") @@ -217,7 +218,6 @@ public class ExcelReportGenerationService { } var createdCell = sheet.getRow(indexToAddRow).createCell(cellsToCopyEntry.getKey().getColumnIndex()); - createdCell.setCellValue(cellsToCopyEntry.getValue().getStringCellValue()); CellStyle newCellStyle = workbook.createCellStyle(); newCellStyle.cloneStyleFrom(cellsToCopyEntry.getValue().getCellStyle()); @@ -274,39 +274,47 @@ public class ExcelReportGenerationService { for (ImagePlaceholder imagePlaceholder : placeholderModel.getImagePlaceholders()) { 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(); + BufferedImage image = ImageCache.getOrLoadImage(imagePlaceholder.getPlaceholder(), imagePlaceholder.getImage()); + if (image != null) { + double factor = calculateScale(image, + PixelUtil.widthUnits2Pixel((short) sheet.getColumnWidth(cell.getColumnIndex())), + PixelUtil.heightUnits2Pixel(cell.getRow().getHeight())); - int pictureIdx = workbook.addPicture(is.readAllBytes(), SXSSFWorkbook.PICTURE_TYPE_JPEG); - is.reset(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(image, "jpg", baos); + baos.flush(); + int pictureIdx = workbook.addPicture(baos.toByteArray(), SXSSFWorkbook.PICTURE_TYPE_JPEG); + baos.close(); - //Returns an object that handles instantiating concrete classes - CreationHelper helper = workbook.getCreationHelper(); - //Create an anchor that is attached to the worksheet + CreationHelper helper = getCreationHelper(workbook); ClientAnchor anchor = helper.createClientAnchor(); anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE); anchor.setCol1(cell.getColumnIndex()); anchor.setRow1(cell.getRowIndex()); - //Creates the top-level drawing patriarch. Drawing drawing = sheet.createDrawingPatriarch(); Picture picture = drawing.createPicture(anchor, pictureIdx); picture.resize(factor); cell.setCellValue(""); - } } } } + private CreationHelper getCreationHelper(SXSSFWorkbook workbook) { + + if (creationHelper == null) { + creationHelper = workbook.getCreationHelper(); + } + return creationHelper; + } + + private String getPlaceholderValue(String placeholder, String dossierName, String filename, PlaceholderModel placeholderModel) { if (placeholder.equals(FORMAT_DATE_ISO_PLACEHOLDER)) { @@ -354,12 +362,10 @@ public class ExcelReportGenerationService { } - private double calculateScale(ByteArrayInputStream imageByteArrayInputStream, float cellWidth, float cellHeight) throws IOException { + private double calculateScale(BufferedImage image, float cellWidth, float cellHeight) { - BufferedImage img = ImageIO.read(imageByteArrayInputStream); - - double imageWidth = img.getWidth(); - double imageHeight = img.getHeight(); + double imageWidth = image.getWidth(); + double imageHeight = image.getHeight(); double widthFactor = cellWidth / imageWidth; double heightFactor = cellHeight / imageHeight; diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelReportTemplateService.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelReportTemplateService.java new file mode 100644 index 0000000..39cc6e8 --- /dev/null +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelReportTemplateService.java @@ -0,0 +1,101 @@ +package com.iqser.red.service.redaction.report.v1.server.service; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +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.shared.model.dossiertemplate.ReportTemplate; +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier; +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel; +import com.iqser.red.service.redaction.report.v1.api.model.ReportType; +import com.iqser.red.service.redaction.report.v1.api.model.StoredFileInformation; +import com.iqser.red.service.redaction.report.v1.server.model.MultiFileWorkbook; +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.model.ReportTemplatesModel; +import com.iqser.red.service.redaction.report.v1.server.storage.ReportStorageService; +import com.iqser.red.service.redaction.report.v1.server.storage.ReportStorageServiceAsyncWrapper; +import com.iqser.red.service.redaction.report.v1.server.utils.TemplateCache; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +@Service +@RequiredArgsConstructor +@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) +@SuppressWarnings("PMD") +public class ExcelReportTemplateService { + + ReportStorageService reportStorageService; + ExcelReportGenerationService excelTemplateReportGenerationService; + GeneratePlaceholderService generatePlaceholderService; + ReportStorageServiceAsyncWrapper reportStorageServiceAsyncWrapper; + + + public CompletableFuture createExcelReportFromTemplateAsync(Dossier dossier, + FileModel fileStatus, + PlaceholderModel placeholderModel, + String templateName, + String downloadId, + List reportEntries, + ReportTemplate reportTemplate) { + + byte[] excelTemplate = TemplateCache.getTemplate(reportTemplate.getStorageId(), reportStorageService); + try (ByteArrayInputStream is = new ByteArrayInputStream(excelTemplate)) { + XSSFWorkbook readWorkbook = new XSSFWorkbook(is); + SXSSFWorkbook writeWorkbook = new SXSSFWorkbook(); + for (Sheet sheet : readWorkbook) { + writeWorkbook.createSheet(sheet.getSheetName()); + } + var excelModel = excelTemplateReportGenerationService.calculateExcelModel(readWorkbook.getSheetAt(0), dossier.getDossierTemplateId()); + if (excelModel.isRssPlaceholdersPresent()) { + generatePlaceholderService.resolveRssValues(fileStatus, placeholderModel); + } + + excelTemplateReportGenerationService.generateExcelReport(reportEntries, + placeholderModel, + templateName, + writeWorkbook, + dossier.getDossierName(), + fileStatus, + excelModel, + true); + byte[] template = excelTemplateReportGenerationService.toByteArray(writeWorkbook); + return reportStorageServiceAsyncWrapper.storeObjectAsync(downloadId, template) + .thenApply(storageId -> new StoredFileInformation(fileStatus.getId(), storageId, ReportType.EXCEL_TEMPLATE_SINGLE_FILE, reportTemplate.getTemplateId(), 0)); + } catch (IOException e) { + CompletableFuture finalResultFuture = new CompletableFuture<>(); + finalResultFuture.completeExceptionally(new RuntimeException("Could not generate single file excel report.", e)); + return finalResultFuture; + } + } + + + public void prepareExcelReportTemplates(String dossierTemplateId, String templateId, ReportTemplate reportTemplate, ReportTemplatesModel reportTemplatesModel) { + + byte[] excelTemplate = TemplateCache.getTemplate(reportTemplate.getStorageId(), reportStorageService); + try (ByteArrayInputStream is = new ByteArrayInputStream(excelTemplate)) { + XSSFWorkbook readWorkbook = new XSSFWorkbook(is); + SXSSFWorkbook writeWorkbook = new SXSSFWorkbook(); + for (Sheet sheet : readWorkbook) { + writeWorkbook.createSheet(sheet.getSheetName()); + } + MultiFileWorkbook multiFileWorkbook = new MultiFileWorkbook(readWorkbook, + writeWorkbook, + templateId, + reportTemplate.getFileName(), + excelTemplateReportGenerationService.calculateExcelModel(readWorkbook.getSheetAt(0), dossierTemplateId)); + reportTemplatesModel.multiFileWorkbookReportTemplates.add(multiFileWorkbook); + } catch (IOException e) { + throw new RuntimeException("Could not generate multifile excel report."); + } + } + +} 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 420970e..d182fe2 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 @@ -4,8 +4,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedList; import java.util.List; +import java.util.concurrent.CompletableFuture; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.xssf.streaming.SXSSFWorkbook; @@ -21,13 +21,15 @@ import com.iqser.red.service.redaction.report.v1.api.model.ReportType; import com.iqser.red.service.redaction.report.v1.api.model.StoredFileInformation; import com.iqser.red.service.redaction.report.v1.server.client.DossierClient; import com.iqser.red.service.redaction.report.v1.server.client.FileStatusClient; -import com.iqser.red.service.redaction.report.v1.server.client.ReportTemplateClient; import com.iqser.red.service.redaction.report.v1.server.model.MultiFileDocument; import com.iqser.red.service.redaction.report.v1.server.model.MultiFileWorkbook; 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.model.ReportTemplatesModel; import com.iqser.red.service.redaction.report.v1.server.settings.ReportTemplateSettings; import com.iqser.red.service.redaction.report.v1.server.storage.ReportStorageService; +import com.iqser.red.service.redaction.report.v1.server.storage.ReportStorageServiceAsyncWrapper; +import com.iqser.red.service.redaction.report.v1.server.utils.TemplateCache; import io.micrometer.core.annotation.Timed; import lombok.AccessLevel; @@ -40,39 +42,41 @@ import lombok.extern.slf4j.Slf4j; @Slf4j @Service @RequiredArgsConstructor +@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) public class ReportGenerationService { - private final ReportStorageService reportStorageService; - private final WordReportGenerationService wordReportGenerationService; - private final EntityLogConverterService entityLogConverterService; - private final FileStatusClient fileStatusClient; - private final DossierClient dossierClient; - private final ReportTemplateClient reportTemplateClient; - private final ExcelReportGenerationService excelTemplateReportGenerationService; - private final GeneratePlaceholderService generatePlaceholderService; - private final ReportTemplateSettings reportTemplateSettings; + ReportStorageService reportStorageService; + WordReportGenerationService wordReportGenerationService; + EntityLogConverterService entityLogConverterService; + FileStatusClient fileStatusClient; + DossierClient dossierClient; + ExcelReportGenerationService excelTemplateReportGenerationService; + GeneratePlaceholderService generatePlaceholderService; + ReportTemplateSettings reportTemplateSettings; + ReportStorageServiceAsyncWrapper reportStorageServiceAsyncWrapper; + ReportTemplateService reportTemplateService; + @SneakyThrows @Timed("redactmanager_generateReports") public String generateReports(ReportRequestMessage reportMessage) { List storedFileInformation = Collections.synchronizedList(new ArrayList<>()); - Dossier dossier = dossierClient.getDossierById(reportMessage.getDossierId(), true, false); - - ReportTemplates reportTemplates = prepareReportTemplates(reportMessage); - + ReportTemplatesModel reportTemplatesModel = reportTemplateService.prepareReportTemplates(reportMessage); var placeholderModel = generatePlaceholderService.buildPlaceholders(dossier); - String downloadId = reportMessage.getDownloadId(); + List> allFutures = new ArrayList<>(); + for (int fileIdIndex = 0; fileIdIndex < reportMessage.getFileIds().size(); fileIdIndex++) { long start = System.currentTimeMillis(); var isLastFile = fileIdIndex == reportMessage.getFileIds().size() - 1; String dossierId = dossier.getId(); - String fileId = reportMessage.getFileIds().get(fileIdIndex); + String fileId = reportMessage.getFileIds() + .get(fileIdIndex); String dossierName = dossier.getDossierName(); var fileStatus = fileStatusClient.getFileStatus(dossierId, fileId); @@ -80,88 +84,57 @@ public class ReportGenerationService { List reportEntries = entityLogConverterService.getReportEntries(dossierId, fileId, fileStatus.isExcluded()); - generateMultiFileExcelReports(reportTemplates.multiFileWorkbookReportTemplates, placeholderModel, fileStatus, isLastFile, dossierName, reportEntries); + generateMultiFileExcelReports(reportTemplatesModel.multiFileWorkbookReportTemplates, placeholderModel, fileStatus, isLastFile, dossierName, reportEntries); - generateMultiFileWordReports(reportTemplates.multiFileDocumentReportTemplates, - storedFileInformation, - dossier, - placeholderModel, - isLastFile, - downloadId, - fileStatus, - reportEntries); + CompletableFuture multiFileWordReportsFuture = generateMultiFileWordReportsAsync(reportTemplatesModel.multiFileDocumentReportTemplates, + storedFileInformation, + dossier, + placeholderModel, + isLastFile, + downloadId, + fileStatus, + reportEntries); + allFutures.add(multiFileWordReportsFuture); - generateSingleFileReports(reportTemplates.singleFilesTemplates, storedFileInformation, dossier, placeholderModel, downloadId, fileStatus, reportEntries); + CompletableFuture singleFileReportsAsync = generateSingleFileReportsAsync(reportTemplatesModel.singleFilesTemplates, + storedFileInformation, + dossier, + placeholderModel, + downloadId, + fileStatus, + reportEntries); + allFutures.add(singleFileReportsAsync); long end = System.currentTimeMillis(); log.info("Successfully processed {}/{} fileIds for downloadId {}, took {}", fileIdIndex + 1, reportMessage.getFileIds().size(), downloadId, end - start); } - for (MultiFileWorkbook multiFileWorkbook : reportTemplates.multiFileWorkbookReportTemplates) { + for (MultiFileWorkbook multiFileWorkbook : reportTemplatesModel.multiFileWorkbookReportTemplates) { byte[] template = excelTemplateReportGenerationService.toByteArray(multiFileWorkbook.getWriteWorkbook()); - String storageId = reportStorageService.storeObject(downloadId, template); - storedFileInformation.add(new StoredFileInformation(null, storageId, ReportType.EXCEL_TEMPLATE_MULTI_FILE, multiFileWorkbook.getTemplateId(), 0)); + CompletableFuture future = reportStorageServiceAsyncWrapper.storeObjectAsync(downloadId, template).thenAccept(storageId -> { + storedFileInformation.add(new StoredFileInformation(null, storageId, ReportType.EXCEL_TEMPLATE_MULTI_FILE, multiFileWorkbook.getTemplateId(), 0)); + }); + allFutures.add(future); } - for (MultiFileDocument multiFileDocument : reportTemplates.multiFileDocumentReportTemplates) { + for (MultiFileDocument multiFileDocument : reportTemplatesModel.multiFileDocumentReportTemplates) { byte[] template = wordReportGenerationService.toByteArray(multiFileDocument.getDocument()); - String storageId = reportStorageService.storeObject(downloadId, template); - storedFileInformation.add(new StoredFileInformation(null, - storageId, - ReportType.WORD_TEMPLATE_MULTI_FILE, - multiFileDocument.getTemplateId(), - multiFileDocument.getDocumentPartNr())); + CompletableFuture future = reportStorageServiceAsyncWrapper.storeObjectAsync(downloadId, template).thenAccept(storageId -> { + storedFileInformation.add(new StoredFileInformation(null, + storageId, + ReportType.WORD_TEMPLATE_MULTI_FILE, + multiFileDocument.getTemplateId(), + multiFileDocument.getDocumentPartNr())); + }); + allFutures.add(future); } + CompletableFuture.allOf(allFutures.toArray(new CompletableFuture[0])).join(); + return reportStorageService.storeReportInformation(reportMessage.getDownloadId(), storedFileInformation); } - private ReportTemplates prepareReportTemplates(ReportRequestMessage reportMessage) { - - var reportTemplates = new ReportTemplates(); - for (String templateId : reportMessage.getTemplateIds()) { - try { - ReportTemplate reportTemplate = reportTemplateClient.getReportTemplate(reportMessage.getDossierTemplateId(), templateId); - if (reportTemplate.isMultiFileReport()) { - if (reportTemplate.getFileName().endsWith(".xlsx")) { - byte[] excelTemplate = reportStorageService.getReportTemplate(reportTemplate.getStorageId()); - try (ByteArrayInputStream is = new ByteArrayInputStream(excelTemplate)) { - XSSFWorkbook readWorkbook = new XSSFWorkbook(is); - SXSSFWorkbook writeWorkbook = new SXSSFWorkbook(); - for (Sheet sheet : readWorkbook) { - writeWorkbook.createSheet(sheet.getSheetName()); - } - MultiFileWorkbook multiFileWorkbook = new MultiFileWorkbook(readWorkbook, - writeWorkbook, - templateId, - reportTemplate.getFileName(), - excelTemplateReportGenerationService.calculateExcelModel(readWorkbook.getSheetAt(0), reportMessage.getDossierTemplateId())); - reportTemplates.multiFileWorkbookReportTemplates.add(multiFileWorkbook); - } catch (IOException e) { - throw new RuntimeException("Could not generate multifile excel report."); - } - } else { - byte[] wordTemplate = reportStorageService.getReportTemplate(reportTemplate.getStorageId()); - try (ByteArrayInputStream is = new ByteArrayInputStream(wordTemplate)) { - XWPFDocument doc = new XWPFDocument(is); - MultiFileDocument multiFileDocument = new MultiFileDocument(wordTemplate, doc, templateId, reportTemplate.getFileName(), 0, 0); - reportTemplates.multiFileDocumentReportTemplates.add(multiFileDocument); - } catch (IOException e) { - throw new RuntimeException("Could not generate multifile word report."); - } - } - } else { - reportTemplates.singleFilesTemplates.add(reportTemplate); - } - } catch (Exception e) { - log.warn("Skipping reportTemplate with id {}", templateId); - } - } - return reportTemplates; - } - - private void generateMultiFileExcelReports(List multiFileWorkbookReportTemplates, PlaceholderModel placeholderModel, FileModel fileStatus, @@ -174,130 +147,88 @@ public class ReportGenerationService { generatePlaceholderService.resolveRssValues(fileStatus, placeholderModel); } excelTemplateReportGenerationService.generateExcelReport(reportEntries, - placeholderModel, - multiFileWorkbook.getTemplateName(), - multiFileWorkbook.getWriteWorkbook(), - dossierName, - fileStatus, - multiFileWorkbook.getExcelModel(), - isLastFile); + placeholderModel, + multiFileWorkbook.getTemplateName(), + multiFileWorkbook.getWriteWorkbook(), + dossierName, + fileStatus, + multiFileWorkbook.getExcelModel(), + isLastFile); } } - private void generateMultiFileWordReports(List multiFileDocumentReportTemplates, - List storedFileInformation, - Dossier dossier, - PlaceholderModel placeholderModel, - boolean isLastFile, - String downloadId, - FileModel fileStatus, - List reportEntries) throws IOException { + private CompletableFuture generateMultiFileWordReportsAsync(List multiFileDocumentReportTemplates, + List storedFileInformation, + Dossier dossier, + PlaceholderModel placeholderModel, + boolean isLastFile, + String downloadId, + FileModel fileStatus, + List reportEntries) { + + List> futures = new ArrayList<>(); for (MultiFileDocument multiFileDocument : multiFileDocumentReportTemplates) { - var numberOfChars = wordReportGenerationService.approxNumberOfChars(reportEntries.stream().findFirst(), fileStatus.getFilename()); + var numberOfChars = wordReportGenerationService.approxNumberOfChars(reportEntries.stream() + .findFirst(), fileStatus.getFilename()); if (multiFileDocument.getNumberOfChars() >= reportTemplateSettings.getMultiFileChunkSize()) { wordReportGenerationService.removePlaceholdersRow(wordReportGenerationService.getRedactionTable(multiFileDocument.getDocument())); byte[] wordDoc = wordReportGenerationService.toByteArray(multiFileDocument.getDocument()); - String storageId = reportStorageService.storeObject(downloadId, wordDoc); - storedFileInformation.add(new StoredFileInformation(null, - storageId, - ReportType.WORD_TEMPLATE_MULTI_FILE, - multiFileDocument.getTemplateId(), - multiFileDocument.getDocumentPartNr())); - multiFileDocument.setDocumentPartNr(multiFileDocument.getDocumentPartNr() + 1); - try (ByteArrayInputStream is = new ByteArrayInputStream(multiFileDocument.getTemplateAsBytes())) { - XWPFDocument doc = new XWPFDocument(is); - multiFileDocument.setDocument(doc); - multiFileDocument.setNumberOfChars(0); - } + CompletableFuture future = reportStorageServiceAsyncWrapper.storeObjectAsync(downloadId, wordDoc).thenAccept(storageId -> { + storedFileInformation.add(new StoredFileInformation(null, + storageId, + ReportType.WORD_TEMPLATE_MULTI_FILE, + multiFileDocument.getTemplateId(), + multiFileDocument.getDocumentPartNr())); + multiFileDocument.setDocumentPartNr(multiFileDocument.getDocumentPartNr() + 1); + try (ByteArrayInputStream is = new ByteArrayInputStream(multiFileDocument.getTemplateAsBytes())) { + XWPFDocument doc = new XWPFDocument(is); + multiFileDocument.setDocument(doc); + multiFileDocument.setNumberOfChars(0); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + futures.add(future); } + numberOfChars = wordReportGenerationService.generateWordReport(reportEntries, - placeholderModel, - multiFileDocument.getTemplateName(), - multiFileDocument.getDocument(), - fileStatus, - dossier, - isLastFile ? isLastFile : multiFileDocument.getNumberOfChars() + numberOfChars >= reportTemplateSettings.getMultiFileChunkSize()); + placeholderModel, + multiFileDocument.getTemplateName(), + multiFileDocument.getDocument(), + fileStatus, + dossier, + isLastFile ? isLastFile : multiFileDocument.getNumberOfChars() + numberOfChars + >= reportTemplateSettings.getMultiFileChunkSize()); + multiFileDocument.setNumberOfChars(multiFileDocument.getNumberOfChars() + numberOfChars); } + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); } - private void generateSingleFileReports(List singleFilesTemplates, - List storedFileInformation, - Dossier dossier, - PlaceholderModel placeholderModel, - String downloadId, - FileModel fileStatus, - List reportEntries) { - - for (ReportTemplate reportTemplate : singleFilesTemplates) { - - storedFileInformation.add(createReportFromTemplate(dossier, fileStatus, placeholderModel, reportTemplate.getFileName(), downloadId, reportEntries, reportTemplate)); - } - } - - - private StoredFileInformation createReportFromTemplate(Dossier dossier, - FileModel fileStatus, - PlaceholderModel placeholderModel, - String templateName, - String downloadId, - List reportEntries, - ReportTemplate reportTemplate) { - - if (reportTemplate.getFileName().endsWith(".xlsx")) { - byte[] excelTemplate = reportStorageService.getReportTemplate(reportTemplate.getStorageId()); - try (ByteArrayInputStream is = new ByteArrayInputStream(excelTemplate)) { - XSSFWorkbook readWorkbook = new XSSFWorkbook(is); - SXSSFWorkbook writeWorkbook = new SXSSFWorkbook(); - for (Sheet sheet : readWorkbook) { - writeWorkbook.createSheet(sheet.getSheetName()); - } - var excelModel = excelTemplateReportGenerationService.calculateExcelModel(readWorkbook.getSheetAt(0), dossier.getDossierTemplateId()); - if (excelModel.isRssPlaceholdersPresent()) { - generatePlaceholderService.resolveRssValues(fileStatus, placeholderModel); - } - - 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(), 0); - } catch (IOException e) { - throw new RuntimeException("Could not generate singlefile excel report."); - } - } else { - byte[] wordTemplate = reportStorageService.getReportTemplate(reportTemplate.getStorageId()); - try (ByteArrayInputStream is = new ByteArrayInputStream(wordTemplate)) { - XWPFDocument doc = new XWPFDocument(is); - 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(), 0); - } catch (IOException e) { - throw new RuntimeException("Could not generate singlefile word report."); - } - } - } - - - @FieldDefaults(makeFinal = true, level = AccessLevel.PUBLIC) - private static final class ReportTemplates { - - List singleFilesTemplates = new LinkedList<>(); - List multiFileWorkbookReportTemplates = new LinkedList<>(); - List multiFileDocumentReportTemplates = new LinkedList<>(); + private CompletableFuture generateSingleFileReportsAsync(List singleFilesTemplates, + List storedFileInformation, + Dossier dossier, + PlaceholderModel placeholderModel, + String downloadId, + FileModel fileStatus, + List reportEntries) { + return CompletableFuture.allOf(singleFilesTemplates.stream() + .map(reportTemplate -> reportTemplateService.createReportFromTemplateAsync(dossier, + fileStatus, + placeholderModel, + reportTemplate.getFileName(), + downloadId, + reportEntries, + reportTemplate).thenAccept(storedFileInformation::add)) + .toArray(CompletableFuture[]::new)); } } diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ReportTemplateService.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ReportTemplateService.java new file mode 100644 index 0000000..a4f8aa9 --- /dev/null +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ReportTemplateService.java @@ -0,0 +1,76 @@ +package com.iqser.red.service.redaction.report.v1.server.service; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.springframework.stereotype.Service; + +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.ReportTemplate; +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier; +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel; +import com.iqser.red.service.redaction.report.v1.api.model.ReportRequestMessage; +import com.iqser.red.service.redaction.report.v1.api.model.StoredFileInformation; +import com.iqser.red.service.redaction.report.v1.server.client.ReportTemplateClient; +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.model.ReportTemplatesModel; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) +public class ReportTemplateService { + + static String XLSX_EXTENSION = ".xlsx"; + ReportTemplateClient reportTemplateClient; + WordReportTemplateService wordReportTemplateService; + ExcelReportTemplateService excelReportTemplateService; + + + public ReportTemplatesModel prepareReportTemplates(ReportRequestMessage reportMessage) { + + ReportTemplatesModel reportTemplates = new ReportTemplatesModel(); + + for (String templateId : reportMessage.getTemplateIds()) { + try { + String dossierTemplatedId = reportMessage.getDossierTemplateId(); + ReportTemplate reportTemplate = reportTemplateClient.getReportTemplate(dossierTemplatedId, templateId); + if (reportTemplate.isMultiFileReport()) { + if (reportTemplate.getFileName().endsWith(XLSX_EXTENSION)) { + excelReportTemplateService.prepareExcelReportTemplates(dossierTemplatedId, templateId, reportTemplate, reportTemplates); + } else { + wordReportTemplateService.prepareWordReportTemplates(templateId, reportTemplate, reportTemplates); + } + } else { + reportTemplates.singleFilesTemplates.add(reportTemplate); + } + } catch (Exception e) { + log.warn("Skipping reportTemplate with id {}, exception {}", templateId, e.getMessage()); + } + } + + return reportTemplates; + } + + + public CompletableFuture createReportFromTemplateAsync(Dossier dossier, + FileModel fileStatus, + PlaceholderModel placeholderModel, + String templateName, + String downloadId, + List reportEntries, + ReportTemplate reportTemplate) { + + if (reportTemplate.getFileName().endsWith(XLSX_EXTENSION)) { + return excelReportTemplateService.createExcelReportFromTemplateAsync(dossier, fileStatus, placeholderModel, templateName, downloadId, reportEntries, reportTemplate); + } else { + return wordReportTemplateService.createWordReportFromTemplateAsync(dossier, fileStatus, placeholderModel, templateName, downloadId, reportEntries, reportTemplate); + } + } + +} 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 e364d2b..2877fb8 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 @@ -68,6 +68,7 @@ import com.iqser.red.service.redaction.report.v1.server.model.PlaceHolderFunctio 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 com.iqser.red.service.redaction.report.v1.server.utils.ImageCache; import io.micrometer.core.annotation.Timed; import lombok.RequiredArgsConstructor; @@ -151,11 +152,13 @@ public class WordReportGenerationService { private void replaceImagePlaceholders(XWPFDocument doc, ImagePlaceholder imagePlaceholder) { - replaceParagraphForImagePlaceholder(doc.getParagraphs(), imagePlaceholder); - for (XWPFTable tbl : doc.getTables()) { - for (XWPFTableRow row : tbl.getRows()) { - for (XWPFTableCell cell : row.getTableCells()) { - replaceParagraphForImagePlaceholder(cell.getParagraphs(), imagePlaceholder); + BufferedImage img = ImageCache.getOrLoadImage(imagePlaceholder.getPlaceholder(), imagePlaceholder.getImage()); + if (img != null) { + for (XWPFTable tbl : doc.getTables()) { + for (XWPFTableRow row : tbl.getRows()) { + for (XWPFTableCell cell : row.getTableCells()) { + replaceParagraphForImagePlaceholder(cell.getParagraphs(), imagePlaceholder, img); + } } } } @@ -202,20 +205,24 @@ public class WordReportGenerationService { } - private void replaceParagraphForImagePlaceholder(List paragraphs, ImagePlaceholder imagePlaceholder) { + private void replaceParagraphForImagePlaceholder(List paragraphs, ImagePlaceholder imagePlaceholder, BufferedImage img) { for (XWPFParagraph p : paragraphs) { String paragraphText = p.getText(); if (paragraphText.contains(imagePlaceholder.getPlaceholder())) { - try (ByteArrayInputStream is2 = new ByteArrayInputStream(imagePlaceholder.getImage())) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(img, "jpg", baos); + baos.flush(); + ByteArrayInputStream is = new ByteArrayInputStream(baos.toByteArray()); + XWPFRun run = p.getRuns().get(0); run.setText("", 0); run.addBreak(); - Dimension2DDouble dim = getImageDimension(is2); - run.addPicture(is2, XWPFDocument.PICTURE_TYPE_JPEG, "image.jpg", Units.toEMU(dim.getWidth()), Units.toEMU(dim.getHeight())); - int size = p.getRuns().size(); - for (int i = 1; i < size; i++) { - p.removeRun(1); + run.addPicture(is, XWPFDocument.PICTURE_TYPE_JPEG, "image.jpg", Units.toEMU(img.getWidth()), Units.toEMU(img.getHeight())); + + for (int i = p.getRuns().size() - 1; i > 0; i--) { + p.removeRun(i); } } catch (IOException | InvalidFormatException e) { throw new RuntimeException(e); diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/WordReportTemplateService.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/WordReportTemplateService.java new file mode 100644 index 0000000..c3c52c9 --- /dev/null +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/WordReportTemplateService.java @@ -0,0 +1,72 @@ +package com.iqser.red.service.redaction.report.v1.server.service; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.springframework.stereotype.Service; + +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.ReportTemplate; +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier; +import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel; +import com.iqser.red.service.redaction.report.v1.api.model.ReportType; +import com.iqser.red.service.redaction.report.v1.api.model.StoredFileInformation; +import com.iqser.red.service.redaction.report.v1.server.model.MultiFileDocument; +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.model.ReportTemplatesModel; +import com.iqser.red.service.redaction.report.v1.server.storage.ReportStorageService; +import com.iqser.red.service.redaction.report.v1.server.storage.ReportStorageServiceAsyncWrapper; +import com.iqser.red.service.redaction.report.v1.server.utils.TemplateCache; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; + +@Service +@RequiredArgsConstructor +@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) +@SuppressWarnings("PMD") +public class WordReportTemplateService { + + ReportStorageService reportStorageService; + ReportStorageServiceAsyncWrapper reportStorageServiceAsyncWrapper; + WordReportGenerationService wordReportGenerationService; + + public CompletableFuture createWordReportFromTemplateAsync(Dossier dossier, + FileModel fileStatus, + PlaceholderModel placeholderModel, + String templateName, + String downloadId, + List reportEntries, + ReportTemplate reportTemplate) { + + byte[] wordTemplate = TemplateCache.getTemplate(reportTemplate.getStorageId(), reportStorageService); + try (ByteArrayInputStream is = new ByteArrayInputStream(wordTemplate)) { + XWPFDocument doc = new XWPFDocument(is); + wordReportGenerationService.generateWordReport(reportEntries, placeholderModel, templateName, doc, fileStatus, dossier, true); + byte[] template = wordReportGenerationService.toByteArray(doc); + return reportStorageServiceAsyncWrapper.storeObjectAsync(downloadId, template) + .thenApply(storageId -> new StoredFileInformation(fileStatus.getId(), storageId, ReportType.WORD_SINGLE_FILE, reportTemplate.getTemplateId(), 0)); + } catch (IOException e) { + CompletableFuture finalResultFuture = new CompletableFuture<>(); + finalResultFuture.completeExceptionally(new RuntimeException("Could not generate single file word report.", e)); + return finalResultFuture; + } + } + + + public void prepareWordReportTemplates(String templateId, ReportTemplate reportTemplate, ReportTemplatesModel reportTemplatesModel) { + + byte[] wordTemplate = TemplateCache.getTemplate(reportTemplate.getStorageId(), reportStorageService); + try (ByteArrayInputStream is = new ByteArrayInputStream(wordTemplate)) { + XWPFDocument doc = new XWPFDocument(is); + MultiFileDocument multiFileDocument = new MultiFileDocument(wordTemplate, doc, templateId, reportTemplate.getFileName(), 0, 0); + reportTemplatesModel.multiFileDocumentReportTemplates.add(multiFileDocument); + } catch (IOException e) { + throw new RuntimeException("Could not generate multifile word report."); + } + } +} diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/storage/ReportStorageServiceAsyncWrapper.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/storage/ReportStorageServiceAsyncWrapper.java new file mode 100644 index 0000000..05efea1 --- /dev/null +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/storage/ReportStorageServiceAsyncWrapper.java @@ -0,0 +1,25 @@ +package com.iqser.red.service.redaction.report.v1.server.storage; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; + +/** + * This is an async wrapper over our storage implementation so that we can store the files for the report asynchronously. + */ +@RequiredArgsConstructor +@Service +public class ReportStorageServiceAsyncWrapper { + + private final ReportStorageService reportStorageService; + private final Executor ioExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + + + public CompletableFuture storeObjectAsync(String downloadId, byte[] data) { + return CompletableFuture.supplyAsync(() -> reportStorageService.storeObject(downloadId, data), ioExecutor); + } +} diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/utils/ImageCache.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/utils/ImageCache.java new file mode 100644 index 0000000..e0407ef --- /dev/null +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/utils/ImageCache.java @@ -0,0 +1,42 @@ +package com.iqser.red.service.redaction.report.v1.server.utils; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; + +import javax.imageio.ImageIO; + +import lombok.extern.slf4j.Slf4j; + +/** + * This is a caching mechanism for images, to avoid loading images with ImageIO.read() inside a loop which is inefficient, + * especially if we load the same image multiple times. + */ +@Slf4j +public class ImageCache { + + private static final Cache cache = Caffeine.newBuilder().maximumSize(100) + .build(); + + + public static BufferedImage getOrLoadImage(String imageKey, byte[] imageData) { + + return cache.get(imageKey, key -> loadImageFromBytes(imageData)); + } + + + private static BufferedImage loadImageFromBytes(byte[] imageData) { + + try (ByteArrayInputStream is = new ByteArrayInputStream(imageData)) { + return ImageIO.read(is); + } catch (IOException e) { + log.error("Error loading image: ", e); + 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/utils/TemplateCache.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/utils/TemplateCache.java new file mode 100644 index 0000000..3d79fbf --- /dev/null +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/utils/TemplateCache.java @@ -0,0 +1,31 @@ +package com.iqser.red.service.redaction.report.v1.server.utils; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.iqser.red.service.redaction.report.v1.server.storage.ReportStorageService; + +import lombok.extern.slf4j.Slf4j; + +/** + * This is a caching mechanism for templates to avoid getting a template from storage for each file when we request a download, + * since the templates are provided at startup. + */ +@Slf4j +public class TemplateCache { + + private static final Cache cache = Caffeine.newBuilder().maximumSize(500).build(); + + + public static byte[] getTemplate(String templateId, ReportStorageService reportStorageService) { + + return cache.get(templateId, reportStorageService::getReportTemplate); + } + + + public static void evictCache(String templateId) { + + log.info("Evicting cache for template {}", templateId); + cache.invalidate(templateId); + } + +} \ No newline at end of file