|
| 1 | +package eu.dzhw.fdz.metadatamanagement.common.service; |
| 2 | + |
| 3 | +import java.io.File; |
| 4 | +import java.io.FileInputStream; |
| 5 | +import java.io.IOException; |
| 6 | +import java.io.StringWriter; |
| 7 | +import java.io.Writer; |
| 8 | +import java.net.URI; |
| 9 | +import java.nio.charset.StandardCharsets; |
| 10 | +import java.nio.file.FileSystem; |
| 11 | +import java.nio.file.FileSystems; |
| 12 | +import java.nio.file.Files; |
| 13 | +import java.nio.file.Path; |
| 14 | +import java.util.ArrayList; |
| 15 | +import java.util.HashMap; |
| 16 | +import java.util.List; |
| 17 | +import java.util.Map; |
| 18 | + |
| 19 | +import org.javers.common.collections.Lists; |
| 20 | +import org.springframework.scheduling.annotation.Async; |
| 21 | + |
| 22 | +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; |
| 23 | +import eu.dzhw.fdz.metadatamanagement.common.domain.Task; |
| 24 | +import eu.dzhw.fdz.metadatamanagement.common.rest.util.ZipUtil; |
| 25 | +import eu.dzhw.fdz.metadatamanagement.datapackagemanagement.service.DataPackageOverviewService; |
| 26 | +import eu.dzhw.fdz.metadatamanagement.datasetmanagement.exception.TemplateIncompleteException; |
| 27 | +import eu.dzhw.fdz.metadatamanagement.datasetmanagement.service.DataSetReportService; |
| 28 | +import eu.dzhw.fdz.metadatamanagement.filemanagement.service.FileService; |
| 29 | +import eu.dzhw.fdz.metadatamanagement.variablemanagement.domain.AccessWays; |
| 30 | +import freemarker.template.Configuration; |
| 31 | +import freemarker.template.Template; |
| 32 | +import freemarker.template.TemplateException; |
| 33 | +import freemarker.template.TemplateExceptionHandler; |
| 34 | +import lombok.RequiredArgsConstructor; |
| 35 | +import lombok.extern.slf4j.Slf4j; |
| 36 | + |
| 37 | +/** |
| 38 | + * Base class for generating reports like {@link DataSetReportService} and |
| 39 | + * {@link DataPackageOverviewService}. |
| 40 | + * |
| 41 | + * It configures freemarker and executes it. The Model for the templates must be provided by the |
| 42 | + * concrete sub classes. |
| 43 | + */ |
| 44 | +@RequiredArgsConstructor |
| 45 | +@Slf4j |
| 46 | +public abstract class AbstractReportService { |
| 47 | + protected final FileService fileService; |
| 48 | + |
| 49 | + protected final TaskManagementService taskService; |
| 50 | + |
| 51 | + protected final MarkdownHelper markdownHelper; |
| 52 | + |
| 53 | + /** |
| 54 | + * The Escape Prefix handles the escaping of special latex signs within data information. This |
| 55 | + * Prefix will be copied before the template source code. |
| 56 | + */ |
| 57 | + protected static final String ESCAPE_PREFIX = |
| 58 | + "<#escape x as x?replace(\"\\\\\", \"\\\\textbackslash{}\")" |
| 59 | + + "?replace(\"{\", \"\\\\{\")?replace(\"}\", \"\\\\}\")" |
| 60 | + + "?replace(\"#\", \"\\\\#\")?replace(\"$\", \"\\\\$\")" |
| 61 | + + "?replace(\"%\", \"\\\\%\")?replace(\"&\", \"\\\\&\")" |
| 62 | + + "?replace(\"^\", \"\\\\textasciicircum{}\")?replace(\"_\", \"\\\\_\")" |
| 63 | + + "?replace(\">\", \"\\\\textgreater{}\")?replace(\"<\", \"\\\\textless{}\")" |
| 64 | + + "?replace(\"~\", \"\\\\textasciitilde{}\")" + "?replace(\"\\r\\n\", \"\\\\par \")" |
| 65 | + + "?replace(\"\\n\", \"\\\\par \")>"; |
| 66 | + |
| 67 | + /** |
| 68 | + * The Escape Suffix closes the escaping prefix. This Prefix will be copied after the template |
| 69 | + * source code. |
| 70 | + */ |
| 71 | + protected static final String ESCAPE_SUFFIX = "</#escape>"; |
| 72 | + |
| 73 | + /** |
| 74 | + * Zip Mime Content Type. |
| 75 | + */ |
| 76 | + protected static final String CONTENT_TYPE_ZIP = "application/zip"; |
| 77 | + |
| 78 | + /** |
| 79 | + * Get all filenames expected to be present in the template zip. |
| 80 | + * |
| 81 | + * @return List of expected filenames |
| 82 | + */ |
| 83 | + private List<String> getExpectedFiles() { |
| 84 | + return Lists.join(getSimpleTemplateFiles(), getComplexTemplateFiles()); |
| 85 | + } |
| 86 | + |
| 87 | + /** |
| 88 | + * Get all filenames of template which can be simply processed. |
| 89 | + * |
| 90 | + * @return List of simple to process files. |
| 91 | + */ |
| 92 | + protected abstract List<String> getSimpleTemplateFiles(); |
| 93 | + |
| 94 | + /** |
| 95 | + * Get all filenames of template which need to be processed in a more complex way (like |
| 96 | + * "Variable.tex"). |
| 97 | + * |
| 98 | + * @return List of simple to process files. |
| 99 | + */ |
| 100 | + protected abstract List<String> getComplexTemplateFiles(); |
| 101 | + |
| 102 | + /** |
| 103 | + * This method load all needed objects from the db for filling the tex template. |
| 104 | + * |
| 105 | + * @param id the id of the dataset or data package. |
| 106 | + * @param version The version of the report as it is displayed in the title. |
| 107 | + * @return A HashMap with all data for the template filling. The Key is the name of the Object, |
| 108 | + * which is used in the template. |
| 109 | + */ |
| 110 | + protected abstract Map<String, Object> loadDataForTemplateFilling(String id, String version); |
| 111 | + |
| 112 | + /** |
| 113 | + * Checks for all files which are included for the tex template. |
| 114 | + * |
| 115 | + * @param zipFileSystem The zip file as file system |
| 116 | + * @return True if all files are included. False min one file is missing. |
| 117 | + */ |
| 118 | + private List<String> validateZipStructure(FileSystem zipFileSystem) { |
| 119 | + List<String> missingTexFiles = new ArrayList<>(); |
| 120 | + |
| 121 | + this.getExpectedFiles().forEach(filename -> { |
| 122 | + Path file = zipFileSystem.getPath(zipFileSystem.getPath("/").toString(), filename); |
| 123 | + if (!Files.exists(file)) { |
| 124 | + missingTexFiles.add(filename); |
| 125 | + } |
| 126 | + }); |
| 127 | + return missingTexFiles; |
| 128 | + } |
| 129 | + |
| 130 | + /** |
| 131 | + * This method save a latex file into GridFS/MongoDB based on a byteArrayOutputStream. |
| 132 | + * |
| 133 | + * @param fileName The name of the file to be saved |
| 134 | + * @return return the file name of the saved latex template in the GridFS / MongoDB. |
| 135 | + * @throws IOException thrown if a stream cannot be closed |
| 136 | + */ |
| 137 | + @SuppressFBWarnings("OBL_UNSATISFIED_OBLIGATION") |
| 138 | + private String saveCompleteZipFile(File zipFile, String fileName) throws IOException { |
| 139 | + // No Update by API, so we have to delete first. |
| 140 | + fileService.deleteTempFile(fileName); |
| 141 | + // Save tex file |
| 142 | + return fileService.saveTempFile(new FileInputStream(zipFile), fileName, CONTENT_TYPE_ZIP); |
| 143 | + } |
| 144 | + |
| 145 | + /** |
| 146 | + * This method fills the tex templates. |
| 147 | + * |
| 148 | + * @param templateContent The content of a tex template. |
| 149 | + * @param templateConfiguration The configuration for freemarker. |
| 150 | + * @param fileName filename of the script which will be filled in this method. |
| 151 | + * @return The filled tex templates as byte array. |
| 152 | + * @throws IOException Handles IO Exception. |
| 153 | + * @throws TemplateException Handles template Exceptions. |
| 154 | + */ |
| 155 | + protected final String fillTemplate(String templateContent, Configuration templateConfiguration, |
| 156 | + Map<String, Object> dataForTemplate, String fileName) throws IOException, TemplateException { |
| 157 | + String templateName = "texTemplate"; |
| 158 | + if (fileName != null && fileName.trim().length() > 0) { |
| 159 | + templateName = fileName; |
| 160 | + } |
| 161 | + |
| 162 | + // Read Template and escape elements |
| 163 | + Template texTemplate = new Template(templateName, |
| 164 | + ESCAPE_PREFIX + templateContent + ESCAPE_SUFFIX, templateConfiguration); |
| 165 | + |
| 166 | + try (Writer stringWriter = new StringWriter()) { |
| 167 | + texTemplate.process(dataForTemplate, stringWriter); |
| 168 | + |
| 169 | + stringWriter.flush(); |
| 170 | + return stringWriter.toString(); |
| 171 | + } |
| 172 | + } |
| 173 | + |
| 174 | + /** |
| 175 | + * This service method will receive a tex template as a string and an id of a data set. With this |
| 176 | + * id, the service will load the data set for receiving all depending information, which are |
| 177 | + * needed for filling of the tex template with data. |
| 178 | + * |
| 179 | + * @param zipTmpFilePath The path to uploaded zip file |
| 180 | + * @param originalName the original name of multipartfile |
| 181 | + * @param id An id of the dataset or data package. |
| 182 | + * @param task the task to update the status of the pro |
| 183 | + * @param version The version of the report as it is displayed in the title. |
| 184 | + * |
| 185 | + * @throws TemplateException Handles templates exceptions. |
| 186 | + * @throws IOException Handles IO Exception for the template. |
| 187 | + */ |
| 188 | + @Async |
| 189 | + public void generateReport(Path zipTmpFilePath, String originalName, String id, Task task, |
| 190 | + String version) { |
| 191 | + log.debug("Start generating report/overview for {} and id {}", originalName, id); |
| 192 | + // Configuration, based on Freemarker Version 2.3.23 |
| 193 | + Configuration templateConfiguration = new Configuration(Configuration.VERSION_2_3_23); |
| 194 | + templateConfiguration.setDefaultEncoding(StandardCharsets.UTF_8.toString()); |
| 195 | + templateConfiguration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); |
| 196 | + templateConfiguration.setNumberFormat("0.######"); |
| 197 | + templateConfiguration.setAPIBuiltinEnabled(true); |
| 198 | + URI uriOfZipFile = URI.create("jar:" + zipTmpFilePath.toUri()); |
| 199 | + // Prepare Zip enviroment config |
| 200 | + Map<String, String> env = new HashMap<>(); |
| 201 | + env.put("create", "true"); |
| 202 | + env.put("encoding", StandardCharsets.UTF_8.name()); |
| 203 | + try (FileSystem zipFileSystem = FileSystems.newFileSystem(uriOfZipFile, env);) { |
| 204 | + // Check missing files. |
| 205 | + log.debug("Check missing files."); |
| 206 | + List<String> missingTexFiles = this.validateZipStructure(zipFileSystem); |
| 207 | + if (!missingTexFiles.isEmpty()) { |
| 208 | + String message = "common.error" + ".files-in-template-zip-incomplete"; |
| 209 | + log.debug(message + missingTexFiles); |
| 210 | + throw new TemplateIncompleteException(message, missingTexFiles); |
| 211 | + } |
| 212 | + // Load data for template only once |
| 213 | + Map<String, Object> dataForTemplate = this.loadDataForTemplateFilling(id, version); |
| 214 | + |
| 215 | + dataForTemplate.put("removeMarkdown", markdownHelper.createRemoveMarkdownMethod()); |
| 216 | + dataForTemplate.put("displayAccessWay", AccessWays.createDisplayAccessWayMethod()); |
| 217 | + |
| 218 | + for (String filename : getSimpleTemplateFiles()) { |
| 219 | + String template = ZipUtil.readFileFromZip(zipFileSystem.getPath(filename)); |
| 220 | + String filledTemplate = |
| 221 | + this.fillTemplate(template, templateConfiguration, dataForTemplate, filename); |
| 222 | + ZipUtil.writeFileToZip(zipFileSystem.getPath(filename), filledTemplate); |
| 223 | + } |
| 224 | + |
| 225 | + // Create Variables pages |
| 226 | + for (String filename : getComplexTemplateFiles()) { |
| 227 | + handleComplexTemplateFile(filename, zipFileSystem, dataForTemplate, templateConfiguration); |
| 228 | + } |
| 229 | + |
| 230 | + // Save into MongoDB / GridFS |
| 231 | + zipFileSystem.close(); |
| 232 | + File zipTmpFile = zipTmpFilePath.toFile(); |
| 233 | + String fileName = this.saveCompleteZipFile(zipTmpFile, originalName); |
| 234 | + log.debug("file saved, start #handletaskDone"); |
| 235 | + taskService.handleTaskDone(task, fileName); |
| 236 | + } catch (RuntimeException e) { |
| 237 | + log.error("failed generating report", e); |
| 238 | + taskService.handleErrorTask(task, e); |
| 239 | + throw e; |
| 240 | + } catch (Exception e) { |
| 241 | + log.error("failed generating report", e); |
| 242 | + taskService.handleErrorTask(task, e); |
| 243 | + } |
| 244 | + } |
| 245 | + |
| 246 | + protected void handleComplexTemplateFile(String templateFilename, FileSystem zipFileSystem, |
| 247 | + Map<String, Object> dataForTemplate, Configuration templateConfiguration) |
| 248 | + throws IOException, TemplateException {} |
| 249 | +} |
0 commit comments