com.xlrit.gears.plugin.docx.XDocReportDocumentProcessor Maven / Gradle / Ivy
package com.xlrit.gears.plugin.docx;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import com.lowagie.text.FontFactory;
import com.xlrit.gears.base.content.ContentRef;
import com.xlrit.gears.base.content.ContentStore;
import com.xlrit.gears.base.model.Document;
import com.xlrit.gears.engine.document.DocumentProcessor;
import com.xlrit.gears.engine.meta.*;
import fr.opensagres.xdocreport.converter.ConverterTypeTo;
import fr.opensagres.xdocreport.converter.ConverterTypeVia;
import fr.opensagres.xdocreport.converter.Options;
import fr.opensagres.xdocreport.core.XDocReportException;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;
@Component
@Order(200)
@RequiredArgsConstructor
public class XDocReportDocumentProcessor implements DocumentProcessor {
private static final Logger LOG = LoggerFactory.getLogger(XDocReportDocumentProcessor.class);
private final ContentStore contentStore;
private final MetaManager metaManager;
private final XDocReportProperties properties;
@PostConstruct
public void initialize() throws IOException {
for (Resource fontResource : findFontResources()) {
String path = fontResourceToFile(fontResource).getAbsolutePath();
FontFactory.register(path);
LOG.info("Registered font from {} ({})", fontResource, path);
}
}
@Override
public boolean supports(Document doc) {
return ("docx".equals(doc.getType()) || "pdf".equals(doc.getType()))
&& doc.getTemplate() != null
&& doc.getTemplate().endsWith(".docx");
}
@Override
public ContentRef process(Document doc) {
Resource template = new ClassPathResource(doc.getTemplate());
IXDocReport report = loadTemplate(doc, template);
IContext context = createContext(doc, report);
byte[] data = exportDocument(report, doc, context);
if (properties.isDebug()) {
try {
properties.getDir().mkdirs();
File debugFile = new File(properties.getDir(), doc.getFilename() + "-debug." + doc.getType());
FileUtils.writeByteArrayToFile(debugFile, data);
LOG.info("Document written to {}", debugFile);
}
catch (IOException e) {
throw new RuntimeException("Unable to write debug output to file");
}
}
return contentStore.putContent(doc.getFilename(), getContentType(doc.getType()), data);
}
private IXDocReport loadTemplate(Document doc, Resource template) {
try (InputStream is = template.getInputStream()) {
return XDocReportRegistry.getRegistry().loadReport(is, TemplateEngineKind.Velocity);
}
catch (IOException | XDocReportException e) {
throw new RuntimeException("Unable to load docx template: '" + doc.getTemplate() + "'", e);
}
}
private IContext createContext(Document doc, IXDocReport report) {
try {
IContext context = report.createContext();
Object params = doc.getParameters();
context.putMap(makeMapFromObject(params));
return context;
}
catch (XDocReportException e) {
throw new RuntimeException("Unable to create context (this SHOULD NEVER happen)");
}
}
private Map makeMapFromObject(Object object) {
Map map = new HashMap<>();
ObjectInfo,BaseField> objectInfo = metaManager.getObjectInfo(object);
for (BaseField field : objectInfo.getFields()) {
Object fieldValue = field.getValue(object);
Object mapValue = convert(fieldValue);
map.put(field.getName(), mapValue);
}
return map;
}
private Object convert(Object fieldValue) {
TypeInfo typeInfo = metaManager.requireTypeInfo(fieldValue);
if (typeInfo instanceof ObjectInfo) {
return makeMapFromObject(fieldValue);
}
if (typeInfo instanceof MultipleInfo) {
return ((Collection>)fieldValue)
.stream()
.map(this::convert)
.collect(Collectors.toList());
}
return fieldValue;
}
private byte[] exportDocument(IXDocReport report, Document doc, IContext context) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
if ("docx".equals(doc.getType())) {
report.process(context, os);
}
else if ("pdf".equals(doc.getType())) {
Options options = Options.getTo(ConverterTypeTo.PDF).via(ConverterTypeVia.XWPF);
report.convert(context, options, os);
}
return os.toByteArray();
}
catch (IOException | XDocReportException ex) {
throw new RuntimeException("Unable to export to byte array output stream", ex);
}
}
private String getContentType(String documentType) {
return switch (documentType) {
case "pdf" -> "application/pdf";
case "docx" -> "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
default -> throw new RuntimeException("Unsupported document type: " + documentType);
};
}
@SneakyThrows
private static Resource[] findFontResources() {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath*:/fonts/*.ttf");
LOG.debug("Found font resources: {}", (Object) resources);
return resources;
}
private static File fontResourceToFile(Resource resource) throws IOException {
if (resource.isFile()) {
File file = resource.getFile();
LOG.debug("Font from {} representable as file {}", resource, file);
return file;
}
try (InputStream is = resource.getInputStream()) {
File file = File.createTempFile("font_", ".ttf");
LOG.debug("Font from {} written to temp file {}", resource, file);
FileUtils.copyInputStreamToFile(is, file);
return file;
}
}
}