Merge branch 'RED-8651' into 'master'
RED-8651 - Improve report generation performance Closes RED-8651 See merge request redactmanager/redaction-report-service!64
This commit is contained in:
commit
dba91f73b1
@ -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<ReportTemplate> getReportTemplatesByPlaceholder(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId, @RequestBody JSONPrimitive<String> placeholder);
|
||||
|
||||
|
||||
@PostMapping(value = REPORT_TEMPLATE_UPLOAD_PATH + TEMPLATE_ID_PATH_VARIABLE)
|
||||
void uploadTemplate(@PathVariable(TEMPLATE_ID) String templateId);
|
||||
|
||||
}
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<ReportTemplate> singleFilesTemplates = Collections.synchronizedList(new ArrayList<>());
|
||||
List<MultiFileWorkbook> multiFileWorkbookReportTemplates = Collections.synchronizedList(new ArrayList<>());
|
||||
List<MultiFileDocument> multiFileDocumentReportTemplates = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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<StoredFileInformation> createExcelReportFromTemplateAsync(Dossier dossier,
|
||||
FileModel fileStatus,
|
||||
PlaceholderModel placeholderModel,
|
||||
String templateName,
|
||||
String downloadId,
|
||||
List<ReportRedactionEntry> 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<StoredFileInformation> 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.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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> 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<CompletableFuture<Void>> 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<ReportRedactionEntry> 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<Void> multiFileWordReportsFuture = generateMultiFileWordReportsAsync(reportTemplatesModel.multiFileDocumentReportTemplates,
|
||||
storedFileInformation,
|
||||
dossier,
|
||||
placeholderModel,
|
||||
isLastFile,
|
||||
downloadId,
|
||||
fileStatus,
|
||||
reportEntries);
|
||||
allFutures.add(multiFileWordReportsFuture);
|
||||
|
||||
generateSingleFileReports(reportTemplates.singleFilesTemplates, storedFileInformation, dossier, placeholderModel, downloadId, fileStatus, reportEntries);
|
||||
CompletableFuture<Void> 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<Void> 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<Void> 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<MultiFileWorkbook> 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<MultiFileDocument> multiFileDocumentReportTemplates,
|
||||
List<StoredFileInformation> storedFileInformation,
|
||||
Dossier dossier,
|
||||
PlaceholderModel placeholderModel,
|
||||
boolean isLastFile,
|
||||
String downloadId,
|
||||
FileModel fileStatus,
|
||||
List<ReportRedactionEntry> reportEntries) throws IOException {
|
||||
private CompletableFuture<Void> generateMultiFileWordReportsAsync(List<MultiFileDocument> multiFileDocumentReportTemplates,
|
||||
List<StoredFileInformation> storedFileInformation,
|
||||
Dossier dossier,
|
||||
PlaceholderModel placeholderModel,
|
||||
boolean isLastFile,
|
||||
String downloadId,
|
||||
FileModel fileStatus,
|
||||
List<ReportRedactionEntry> reportEntries) {
|
||||
|
||||
List<CompletableFuture<Void>> 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<Void> 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<ReportTemplate> singleFilesTemplates,
|
||||
List<StoredFileInformation> storedFileInformation,
|
||||
Dossier dossier,
|
||||
PlaceholderModel placeholderModel,
|
||||
String downloadId,
|
||||
FileModel fileStatus,
|
||||
List<ReportRedactionEntry> 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<ReportRedactionEntry> 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<ReportTemplate> singleFilesTemplates = new LinkedList<>();
|
||||
List<MultiFileWorkbook> multiFileWorkbookReportTemplates = new LinkedList<>();
|
||||
List<MultiFileDocument> multiFileDocumentReportTemplates = new LinkedList<>();
|
||||
private CompletableFuture<Void> generateSingleFileReportsAsync(List<ReportTemplate> singleFilesTemplates,
|
||||
List<StoredFileInformation> storedFileInformation,
|
||||
Dossier dossier,
|
||||
PlaceholderModel placeholderModel,
|
||||
String downloadId,
|
||||
FileModel fileStatus,
|
||||
List<ReportRedactionEntry> reportEntries) {
|
||||
|
||||
return CompletableFuture.allOf(singleFilesTemplates.stream()
|
||||
.map(reportTemplate -> reportTemplateService.createReportFromTemplateAsync(dossier,
|
||||
fileStatus,
|
||||
placeholderModel,
|
||||
reportTemplate.getFileName(),
|
||||
downloadId,
|
||||
reportEntries,
|
||||
reportTemplate).thenAccept(storedFileInformation::add))
|
||||
.toArray(CompletableFuture[]::new));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<StoredFileInformation> createReportFromTemplateAsync(Dossier dossier,
|
||||
FileModel fileStatus,
|
||||
PlaceholderModel placeholderModel,
|
||||
String templateName,
|
||||
String downloadId,
|
||||
List<ReportRedactionEntry> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<XWPFParagraph> paragraphs, ImagePlaceholder imagePlaceholder) {
|
||||
private void replaceParagraphForImagePlaceholder(List<XWPFParagraph> 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);
|
||||
|
||||
@ -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<StoredFileInformation> createWordReportFromTemplateAsync(Dossier dossier,
|
||||
FileModel fileStatus,
|
||||
PlaceholderModel placeholderModel,
|
||||
String templateName,
|
||||
String downloadId,
|
||||
List<ReportRedactionEntry> 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<StoredFileInformation> 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<String> storeObjectAsync(String downloadId, byte[] data) {
|
||||
return CompletableFuture.supplyAsync(() -> reportStorageService.storeObject(downloadId, data), ioExecutor);
|
||||
}
|
||||
}
|
||||
@ -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<String, BufferedImage> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<String, byte[]> 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);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user