From 666e6b1ba21bee2b0f1bff5ed8d9414e1c3f922f Mon Sep 17 00:00:00 2001 From: George Kalampokis Date: Mon, 18 Dec 2023 11:05:27 +0200 Subject: [PATCH] Initial Commit --- .gitignore | 2 + core/pom.xml | 148 +++ .../configuration/FilePathConfiguration.java | 9 + .../configuration/FilePathProperties.java | 30 + .../FileStorageConfiguration.java | 9 + .../configuration/FileStorageProperties.java | 18 + .../configuration/PdfConfiguration.java | 9 + .../configuration/PdfProperties.java | 18 + .../executor/WordFileTransformer.java | 623 +++++++++ .../eudat/file/transformer/model/PidLink.java | 22 + .../transformer/model/enums/FileFormats.java | 32 + .../model/file/FileEnvelopeInternal.java | 28 + .../transformer/model/visibility/Rule.java | 40 + .../model/visibility/RuleMapper.java | 53 + .../visibility/VisibilityContext.java | 44 + .../services/visibility/VisibilityRule.java | 27 + .../visibility/VisibilityRuleService.java | 17 + .../visibility/VisibilityRuleServiceImpl.java | 73 ++ .../visibility/VisibilityRuleSource.java | 25 + .../DescriptionTemplateService.java | 42 + .../transformer/utils/interfaces/Applier.java | 8 + .../utils/interfaces/ApplierWithValue.java | 8 + .../utils/interfaces/Cloneable.java | 8 + .../utils/interfaces/ModelSerializer.java | 8 + .../transformer/utils/json/JavaToJson.java | 13 + .../transformer/utils/json/JsonSearcher.java | 81 ++ .../utils/json/MultiDateDeserializer.java | 41 + .../file/transformer/utils/pdf/PDFUtils.java | 36 + .../file/transformer/utils/pid/PidLoader.java | 36 + .../utils/types/ParagraphStyle.java | 49 + .../transformer/utils/types/TextStyle.java | 31 + .../utils/word/HtmlToWorldBuilder.java | 289 ++++ .../transformer/utils/word/WordBuilder.java | 1166 +++++++++++++++++ .../utils/word/XWPFHtmlDocument.java | 63 + .../utils/word/XWPFHtmlRelation.java | 11 + pom.xml | 29 + web/pom.xml | 59 + .../FileTransformerApplication.java | 16 + .../config/SecurityConfiguration.java | 76 ++ .../controller/FileTransformerController.java | 53 + web/src/main/resources/config/application.yml | 11 + web/src/main/resources/config/cache.yml | 16 + web/src/main/resources/config/pdf.yml | 3 + web/src/main/resources/config/security.yml | 20 + web/src/main/resources/config/server.yml | 12 + web/src/main/resources/config/storage.yml | 7 + web/src/main/resources/documents/h2020.docx | Bin 0 -> 30266 bytes .../resources/documents/h2020_dataset.docx | Bin 0 -> 20315 bytes web/src/main/resources/documents/styles.xml | 2 + web/src/main/resources/pidLinks.json | 100 ++ 50 files changed, 3521 insertions(+) create mode 100644 .gitignore create mode 100644 core/pom.xml create mode 100644 core/src/main/java/eu/eudat/file/transformer/configuration/FilePathConfiguration.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/configuration/FilePathProperties.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/configuration/FileStorageConfiguration.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/configuration/FileStorageProperties.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/configuration/PdfConfiguration.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/configuration/PdfProperties.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/executor/WordFileTransformer.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/model/PidLink.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/model/enums/FileFormats.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/model/file/FileEnvelopeInternal.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/model/visibility/Rule.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/model/visibility/RuleMapper.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityContext.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRule.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRuleService.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRuleServiceImpl.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRuleSource.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/descriptionTemplate/DescriptionTemplateService.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/interfaces/Applier.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/interfaces/ApplierWithValue.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/interfaces/Cloneable.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/interfaces/ModelSerializer.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/json/JavaToJson.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/json/JsonSearcher.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/json/MultiDateDeserializer.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/pdf/PDFUtils.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/pid/PidLoader.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/types/ParagraphStyle.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/types/TextStyle.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/word/HtmlToWorldBuilder.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/word/WordBuilder.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/word/XWPFHtmlDocument.java create mode 100644 core/src/main/java/eu/eudat/file/transformer/utils/word/XWPFHtmlRelation.java create mode 100644 pom.xml create mode 100644 web/pom.xml create mode 100644 web/src/main/java/eu/eudat/file/transformer/FileTransformerApplication.java create mode 100644 web/src/main/java/eu/eudat/file/transformer/config/SecurityConfiguration.java create mode 100644 web/src/main/java/eu/eudat/file/transformer/controller/FileTransformerController.java create mode 100644 web/src/main/resources/config/application.yml create mode 100644 web/src/main/resources/config/cache.yml create mode 100644 web/src/main/resources/config/pdf.yml create mode 100644 web/src/main/resources/config/security.yml create mode 100644 web/src/main/resources/config/server.yml create mode 100644 web/src/main/resources/config/storage.yml create mode 100644 web/src/main/resources/documents/h2020.docx create mode 100644 web/src/main/resources/documents/h2020_dataset.docx create mode 100644 web/src/main/resources/documents/styles.xml create mode 100644 web/src/main/resources/pidLinks.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..92322c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +target/ diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 0000000..849dad6 --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,148 @@ + + + 4.0.0 + + gr.cite.opendmp + file-transformer-document-parent + ${revision} + .. + + + file-transformer-document + ${revision} + jar + + OpenDMP File Trasformer For MS-Doc and PDF + OpenDMP File Trasformer For MS-Doc and PDF + https://code-repo.d4science.org/MaDgiK-CITE/repository-deposit-zenodo + + + MIT License + https://code-repo.d4science.org/MaDgiK-CITE/repository-deposit-zenodo/src/branch/master/LICENSE.txt + repo + + + + + CITE S.A. + maven-central@cite.gr + CITE S.A. + https://www.cite.gr + + + + scm:git:git://code-repo.d4science.org + scm:git:ssh://code-repo.d4science.org + https://code-repo.d4science.org/MaDgiK-CITE/repository-deposit-zenodo + + + + 21 + 21 + 21 + 1.0.0-SNAPSHOT + + + + + gr.cite.opendmp + file-transformer-base + 1.0-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.apache.poi + poi + 5.2.3 + + + + org.apache.poi + poi-ooxml + 5.2.3 + + + + org.apache.xmlgraphics + fop + 2.8 + + + + org.jsoup + jsoup + 1.16.1 + + + + org.json + json + 20160810 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + + + + + + + maven-assembly-plugin + + + package + + single + + + + + + + eu.eudat.EuDatApplication + + + + jar-with-dependencies + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.4.1 + + + package + + shade + + + + + *:* + + module-info.class + + + + + + org.json + zenodorepository.shaded.org.json + + + + + + + + + diff --git a/core/src/main/java/eu/eudat/file/transformer/configuration/FilePathConfiguration.java b/core/src/main/java/eu/eudat/file/transformer/configuration/FilePathConfiguration.java new file mode 100644 index 0000000..ce08a8f --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/configuration/FilePathConfiguration.java @@ -0,0 +1,9 @@ +package eu.eudat.file.transformer.configuration; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties({FilePathProperties.class}) +public class FilePathConfiguration { +} diff --git a/core/src/main/java/eu/eudat/file/transformer/configuration/FilePathProperties.java b/core/src/main/java/eu/eudat/file/transformer/configuration/FilePathProperties.java new file mode 100644 index 0000000..b17de61 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/configuration/FilePathProperties.java @@ -0,0 +1,30 @@ +package eu.eudat.file.transformer.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; + +@ConfigurationProperties(prefix = "file.template") +public class FilePathProperties { + private final String wordTemplate; + private final String pidTemplate; + private final String wordDescriptionTemplate; + + @ConstructorBinding + public FilePathProperties(String wordTemplate, String pidTemplate, String wordDescriptionTemplate) { + this.wordTemplate = wordTemplate; + this.pidTemplate = pidTemplate; + this.wordDescriptionTemplate = wordDescriptionTemplate; + } + + public String getWordTemplate() { + return wordTemplate; + } + + public String getPidTemplate() { + return pidTemplate; + } + + public String getWordDescriptionTemplate() { + return wordDescriptionTemplate; + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/configuration/FileStorageConfiguration.java b/core/src/main/java/eu/eudat/file/transformer/configuration/FileStorageConfiguration.java new file mode 100644 index 0000000..4f6122b --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/configuration/FileStorageConfiguration.java @@ -0,0 +1,9 @@ +package eu.eudat.file.transformer.configuration; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties({FileStorageProperties.class}) +public class FileStorageConfiguration { +} diff --git a/core/src/main/java/eu/eudat/file/transformer/configuration/FileStorageProperties.java b/core/src/main/java/eu/eudat/file/transformer/configuration/FileStorageProperties.java new file mode 100644 index 0000000..c1f8ac1 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/configuration/FileStorageProperties.java @@ -0,0 +1,18 @@ +package eu.eudat.file.transformer.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; + +@ConfigurationProperties(prefix = "file.storage") +public class FileStorageProperties { + private final String temp; + + @ConstructorBinding + public FileStorageProperties(String temp) { + this.temp = temp; + } + + public String getTemp() { + return temp; + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/configuration/PdfConfiguration.java b/core/src/main/java/eu/eudat/file/transformer/configuration/PdfConfiguration.java new file mode 100644 index 0000000..a24db19 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/configuration/PdfConfiguration.java @@ -0,0 +1,9 @@ +package eu.eudat.file.transformer.configuration; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties({PdfProperties.class}) +public class PdfConfiguration { +} diff --git a/core/src/main/java/eu/eudat/file/transformer/configuration/PdfProperties.java b/core/src/main/java/eu/eudat/file/transformer/configuration/PdfProperties.java new file mode 100644 index 0000000..d794a2b --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/configuration/PdfProperties.java @@ -0,0 +1,18 @@ +package eu.eudat.file.transformer.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; + +@ConfigurationProperties(prefix = "pdf.converter") +public class PdfProperties { + private final String url; + + @ConstructorBinding + public PdfProperties(String url) { + this.url = url; + } + + public String getUrl() { + return url; + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/executor/WordFileTransformer.java b/core/src/main/java/eu/eudat/file/transformer/executor/WordFileTransformer.java new file mode 100644 index 0000000..cf15f47 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/executor/WordFileTransformer.java @@ -0,0 +1,623 @@ +package eu.eudat.file.transformer.executor; + +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.eudat.file.transformer.configuration.FilePathProperties; +import eu.eudat.file.transformer.configuration.FileStorageProperties; +import eu.eudat.file.transformer.configuration.PdfProperties; +import eu.eudat.file.transformer.entities.user.composite.PagedDatasetProfile; +import eu.eudat.file.transformer.enums.*; +import eu.eudat.file.transformer.enums.ReferenceType; +import eu.eudat.file.transformer.model.*; +import eu.eudat.file.transformer.model.dmpblueprintdefinition.ExtraField; +import eu.eudat.file.transformer.model.dmpblueprintdefinition.Field; +import eu.eudat.file.transformer.model.dmpblueprintdefinition.Section; +import eu.eudat.file.transformer.model.dmpblueprintdefinition.SystemField; +import eu.eudat.file.transformer.model.enums.FileFormats; +import eu.eudat.file.transformer.model.file.FileEnvelope; +import eu.eudat.file.transformer.model.file.FileEnvelopeInternal; +import eu.eudat.file.transformer.services.visibility.VisibilityRuleService; +import eu.eudat.file.transformer.services.visibility.VisibilityRuleServiceImpl; +import eu.eudat.file.transformer.utils.descriptionTemplate.DescriptionTemplateService; +import eu.eudat.file.transformer.utils.pdf.PDFUtils; +import eu.eudat.file.transformer.utils.types.ParagraphStyle; +import eu.eudat.file.transformer.utils.word.WordBuilder; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.apache.poi.xwpf.usermodel.XWPFParagraph; +import org.apache.poi.xwpf.usermodel.XWPFRun; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.util.ResourceUtils; + +import javax.management.InvalidApplicationException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.time.Instant; +import java.util.*; +@Component +public class WordFileTransformer implements FileTransformerExecutor { + private final static Logger logger = LoggerFactory.getLogger(WordFileTransformer.class); + + private final FilePathProperties fileTemplateProperties; + private final FileStorageProperties fileStorageProperties; + private final PdfProperties pdfProperties; + private final ApplicationContext applicationContext; + private final ObjectMapper objectMapper; + + @Autowired + public WordFileTransformer(FilePathProperties fileTemplateProperties, FileStorageProperties fileStorageProperties, PdfProperties pdfProperties, ApplicationContext applicationContext) { + this.fileTemplateProperties = fileTemplateProperties; + this.fileStorageProperties = fileStorageProperties; + this.pdfProperties = pdfProperties; + this.applicationContext = applicationContext; + this.objectMapper = new ObjectMapper(); + } + + @Override + public FileEnvelope exportDmp(DmpFileTransformerModel dmp, ExtraPropertiesModel properties) throws IOException { + FileFormats fileFormat = FileFormats.of(properties.getFormat()); + return switch (fileFormat) { + case DOCX -> getWordDocument(dmp); + case PDF -> { + FileEnvelopeInternal wordFile = getWordDocument(dmp, true); + yield getPdfDocument(wordFile); + } + }; + } + + @Override + public FileEnvelope exportDescription(DescriptionFileTransformerModel descriptionFileTransformerModel, ExtraPropertiesModel properties) throws InvalidApplicationException, IOException { + FileFormats fileFormat = FileFormats.of(properties.getFormat()); + return switch (fileFormat) { + case DOCX -> getDescriptionWordDocument(descriptionFileTransformerModel); + case PDF -> { + FileEnvelopeInternal wordFile = getDescriptionWordDocumentInternal(descriptionFileTransformerModel); + yield getPdfDocument(wordFile); + } + }; + } + + + @Override + public DmpFileTransformerModel importFileToDmp(FileEnvelope envelope) { + //Nothing to do here + return null; + } + + @Override + public DescriptionFileTransformerModel importFileToDescription(FileEnvelope envelope) { + return null; + } + + @Override + public List getSupportedFileFormats() { + return Arrays.stream(FileFormats.values()).map(FileFormats::getValue).toList(); + } + + private FileEnvelope getPdfDocument(FileEnvelopeInternal wordFile) throws IOException { + File pdfFile = PDFUtils.convertToPDF(wordFile, fileStorageProperties.getTemp(), pdfProperties.getUrl()); + FileEnvelope result = new FileEnvelope(); + result.setFilename(wordFile.getFilename().replaceAll(".docx", ".pdf")); + try (FileInputStream fis = new FileInputStream(pdfFile)) { + result.setFile(fis.readAllBytes()); + } + return result; + } + + private FileEnvelope getWordDocument(DmpFileTransformerModel dmp) throws IOException { + FileEnvelopeInternal wordFile = getWordDocument(dmp, true); + FileEnvelope result = new FileEnvelope(); + result.setFilename(wordFile.getFilename()); + try (FileInputStream fis = new FileInputStream(wordFile.getFile())) { + result.setFile(fis.readAllBytes()); + } + return result; + } + + private FileEnvelopeInternal getWordDocument(DmpFileTransformerModel dmpEntity, Boolean versioned) throws IOException { + WordBuilder wordBuilder = new WordBuilder(fileTemplateProperties, fileStorageProperties); + VisibilityRuleService visibilityRuleService = new VisibilityRuleServiceImpl(); + XWPFDocument document = new XWPFDocument(new FileInputStream(ResourceUtils.getFile(fileTemplateProperties.getWordTemplate()))); + + wordBuilder.fillFirstPage(dmpEntity, null, document, false); + + List funders = dmpEntity.getDmpReferences().stream().filter(dmpReference -> dmpReference.getReference().getType().equals(ReferenceType.Funder) && dmpReference.getReference().getIsActive().equals(IsActive.Active)).map(DmpReference::getReference).toList(); + List grants = dmpEntity.getDmpReferences().stream().filter(referenceFileModel -> referenceFileModel.getReference().getType().equals(ReferenceType.Grants) && referenceFileModel.getReference().getIsActive().equals(IsActive.Active)).map(DmpReference::getReference).toList(); + List researchers = dmpEntity.getDmpReferences().stream().filter(referenceFileModel -> referenceFileModel.getReference().getType().equals(ReferenceType.Researcher)).map(DmpReference::getReference).toList(); + List organizations = dmpEntity.getDmpReferences().stream().filter(referenceFileModel -> referenceFileModel.getReference().getType().equals(ReferenceType.Organizations)).map(DmpReference::getReference).toList(); + List projects = dmpEntity.getDmpReferences().stream().filter(referenceFileModel -> referenceFileModel.getReference().getType().equals(ReferenceType.Project) && referenceFileModel.getReference().getIsActive().equals(IsActive.Active)).map(DmpReference::getReference).toList(); + +// int powered_pos = document.getParagraphs().size() - 3; + int powered_pos = wordBuilder.findPosOfPoweredBy(document); + XWPFParagraph powered_par = null; + XWPFParagraph argos_img_par = null; + if(powered_pos != -1) { + powered_par = document.getParagraphArray(powered_pos); + argos_img_par = document.getParagraphArray(powered_pos + 1); + } + + +// // DMP info on top of the document. +// wordBuilder.addParagraphContent("Data Management Plan Information", document, ParagraphStyle.HEADER1, BigInteger.ZERO); +// // DMP title custom style. +// wordBuilder.addParagraphContent(dmpEntity.getLabel(), document, ParagraphStyle.HEADER2, BigInteger.ZERO); +// wordBuilder.addParagraphContent(dmpEntity.getDescription(), document, ParagraphStyle.HTML, BigInteger.ZERO); +// +// wordBuilder.addParagraphContent("Funder", document, ParagraphStyle.HEADER3, BigInteger.ZERO); +// if (dmpEntity.getGrant().getFunder() != null) +// wordBuilder.addParagraphContent(dmpEntity.getGrant().getFunder().getLabel(), document, ParagraphStyle.TEXT, BigInteger.ZERO); +// +// wordBuilder.addParagraphContent("Grant", document, ParagraphStyle.HEADER3, BigInteger.ZERO); +// wordBuilder.addParagraphContent(dmpEntity.getGrant().getLabel(), document, ParagraphStyle.TEXT, BigInteger.ZERO); +// +// wordBuilder.addParagraphContent("Organisations", document, ParagraphStyle.HEADER3, BigInteger.ZERO); +// if (dmpEntity.getOrganisations().size() > 0) { +// wordBuilder.addParagraphContent(dmpEntity.getOrganisations().stream().map(Organisation::getLabel).collect(Collectors.joining(", ")) +// , document, ParagraphStyle.TEXT, BigInteger.ZERO); +// } +// +// wordBuilder.addParagraphContent("Researchers", document, ParagraphStyle.HEADER3, BigInteger.ZERO); +// if (dmpEntity.getResearchers().size() > 0) { +// wordBuilder.addParagraphContent(dmpEntity.getResearchers().stream().map(Researcher::getLabel).collect(Collectors.joining(", ")) +// , document, ParagraphStyle.TEXT, BigInteger.ZERO); +// } +// +// /*wordBuilder.addParagraphContent("DMP Profile", document, ParagraphStyle.HEADER2, BigInteger.ZERO); +// if (dmpEntity.getProfile() != null){ +// wordBuilder.addParagraphContent(dmpEntity.getProfile().getLabel(), document, ParagraphStyle.TEXT, BigInteger.ZERO); +// }*/ +// +// // Page break at the end of the DMP title. +// XWPFParagraph parBreakDMP = document.createParagraph(); +// parBreakDMP.setPageBreak(true); +// +// wordBuilder.addParagraphContent("Datasets", document, ParagraphStyle.HEADER1, BigInteger.ZERO); +// // Space below Datasets. +// XWPFParagraph parBreakDatasets = document.createParagraph(); + + DmpBlueprint dmpBlueprint = dmpEntity.getBlueprint(); + for(Section section: dmpBlueprint.getDefinition().getSections()){ + wordBuilder.addParagraphContent("Section " + section.getOrdinal(), document, ParagraphStyle.HEADER1, BigInteger.ZERO, 0); + XWPFParagraph sectionInfoParagraph = document.createParagraph(); + sectionInfoParagraph.setSpacingBetween(1.0); + XWPFRun runSectionTitle = sectionInfoParagraph.createRun(); + runSectionTitle.setText("Title: "); + runSectionTitle.setColor("000000"); + XWPFRun runSectionTitleText = sectionInfoParagraph.createRun(); + runSectionTitleText.setText(section.getLabel()); + runSectionTitleText.setColor("116a78"); + XWPFParagraph sectionDescriptionParagraph = document.createParagraph(); + XWPFRun runSectionDescription = sectionDescriptionParagraph.createRun(); + runSectionDescription.setText("Description: "); + runSectionDescription.setColor("000000"); + XWPFRun runSectionDescriptionText = sectionDescriptionParagraph.createRun(); + runSectionDescriptionText.setText(section.getDescription()); + runSectionDescriptionText.setColor("116a78"); + + wordBuilder.addParagraphContent("Section Fields", document, ParagraphStyle.HEADER2, BigInteger.ZERO, 0); + section.getFields().sort(Comparator.comparingInt(Field::getOrdinal)); + for(Field field: section.getFields()){ + if(field.getCategory() == DmpBlueprintFieldCategory.System){ + SystemField systemField = (SystemField) field; + XWPFParagraph systemFieldParagraph = document.createParagraph(); + systemFieldParagraph.setSpacingBetween(1.0); + XWPFRun runSyStemFieldTitle = systemFieldParagraph.createRun(); + runSyStemFieldTitle.setText("Title: "); + runSyStemFieldTitle.setColor("000000"); + XWPFRun runSystemFieldTitleText = systemFieldParagraph.createRun(); + runSystemFieldTitleText.setText(systemField.getLabel()); + runSystemFieldTitleText.setColor("116a78"); + if(systemField.getDescription() != null && !systemField.getDescription().isEmpty()){ + XWPFParagraph systemFieldDescription = document.createParagraph(); + systemFieldDescription.setSpacingBetween(1.0); + XWPFRun runSyStemFieldDescription = systemFieldDescription.createRun(); + runSyStemFieldDescription.setText("Description: "); + runSyStemFieldDescription.setColor("000000"); + XWPFRun runSystemFieldDescriptionText = systemFieldDescription.createRun(); + runSystemFieldDescriptionText.setText(systemField.getDescription()); + runSystemFieldDescriptionText.setColor("116a78"); + } + XWPFParagraph systemFieldInput = document.createParagraph(); + systemFieldInput.setSpacingBetween(1.0); + XWPFRun runInput = systemFieldInput.createRun(); + runInput.setText("Input: "); + runInput.setColor("000000"); + Map dmpProperties = objectMapper.readValue(dmpEntity.getProperties(), HashMap.class); + switch (systemField.getSystemFieldType()) { + case Text: + XWPFRun runTitle = systemFieldInput.createRun(); + runTitle.setText(dmpEntity.getLabel()); + runTitle.setColor("116a78"); + break; + case HtmlText: + XWPFRun runDescription = systemFieldInput.createRun(); + runDescription.setText(dmpEntity.getDescription()); + runDescription.setColor("116a78"); + break; + case Researchers: + for(Reference researcher: researchers){ + XWPFRun runResearcher = systemFieldInput.createRun(); + runResearcher.setText("• " + researcher.getLabel()); + runResearcher.setColor("116a78"); + } + break; + case Organizations: + for(Reference organisation: organizations){ + XWPFRun runOrganisation = systemFieldInput.createRun(); + runOrganisation.setText("• " + organisation.getLabel()); + runOrganisation.setColor("116a78"); + } + break; + /* case Language: + XWPFRun runLanguage = systemFieldInput.createRun(); + runLanguage.setText(dmpProperties.get("language").toString()); + runLanguage.setColor("116a78"); + break;*/ + case Contact: + XWPFRun runContact = systemFieldInput.createRun(); + runContact.setText(dmpEntity.getCreator().getName()); + runContact.setColor("116a78"); + break; + case Funder: + if (!funders.isEmpty()) { + XWPFRun runFunder = systemFieldInput.createRun(); + runFunder.setText(funders.get(0).getLabel()); + runFunder.setColor("116a78"); + } + break; + case Grant: + if (!grants.isEmpty()) { + XWPFRun runGrant = systemFieldInput.createRun(); + runGrant.setText(grants.get(0).getLabel()); + runGrant.setColor("116a78"); + } + break; + case Project: + if (!projects.isEmpty()) { + XWPFRun runProject = systemFieldInput.createRun(); + runProject.setText(projects.get(0).getLabel()); + runProject.setColor("116a78"); + } + break; + case License: + if (dmpProperties.containsKey("license")) { + XWPFRun runLicense = systemFieldInput.createRun(); + runLicense.setText(dmpProperties.get("license").toString()); + runLicense.setColor("116a78"); + } + break; + case AccessRights: + if (dmpProperties.containsKey("visible")) { + XWPFRun runAccessRights = systemFieldInput.createRun(); + runAccessRights.setText(dmpProperties.get("visible").toString()); + runAccessRights.setColor("116a78"); + } + break; + } + document.createParagraph(); + } + else if(field.getCategory() == DmpBlueprintFieldCategory.Extra){ + ExtraField extraField = (ExtraField) field; + XWPFParagraph extraFieldParagraph = document.createParagraph(); + extraFieldParagraph.setSpacingBetween(1.0); + XWPFRun runExtraFieldLabel = extraFieldParagraph.createRun(); + runExtraFieldLabel.setText(extraField.getLabel()); + runExtraFieldLabel.setColor("116a78"); + if(extraField.getDescription() != null && !extraField.getDescription().isEmpty()){ + XWPFRun runExtraFieldDescription = extraFieldParagraph.createRun(); + runExtraFieldDescription.setText(extraField.getDescription()); + runExtraFieldDescription.setColor("116a78"); + } + XWPFRun runExtraFieldInput = extraFieldParagraph.createRun(); + Map dmpProperties = objectMapper.readValue(dmpEntity.getProperties(), HashMap.class); + if (dmpProperties.containsKey(field.getId()) && dmpProperties.get(field.getId()) != null) { + runExtraFieldInput.setText((String) dmpProperties.get(field.getId())); + } + runExtraFieldInput.setColor("116a78"); + } + } + + final Boolean isFinalized = dmpEntity.getStatus() == DmpStatus.Finalized; + final Boolean isPublic = dmpEntity.getPublicAfter() != null && dmpEntity.getPublicAfter().isAfter(Instant.now()); + List descriptions = dmpEntity.getDescriptions().stream() + .filter(item -> item.getStatus() != DescriptionStatus.Canceled) + .filter(item -> item.getIsActive() != IsActive.Inactive) + .filter(item -> !isPublic && !isFinalized || item.getStatus() == DescriptionStatus.Finalized) + .filter(item -> item.getDmpDescriptionTemplate().getSectionId().equals(section.getId())) //TODO + .sorted(Comparator.comparing(DescriptionFileTransformerModel::getCreatedAt)).toList(); + List descriptionTemplates = descriptions.stream().map(DescriptionFileTransformerModel::getDescriptionTemplate).toList(); + + if(!descriptionTemplates.isEmpty()){ + wordBuilder.addParagraphContent("Section descriptions", document, ParagraphStyle.HEADER2, BigInteger.ZERO, 0); + wordBuilder.addParagraphContent("Description Templates", document, ParagraphStyle.HEADER4, BigInteger.ZERO, 0); + for(DescriptionTemplate descriptionTemplateEntity : descriptionTemplates){ + XWPFParagraph templateParagraph = document.createParagraph(); + XWPFRun runTemplateLabel = templateParagraph.createRun(); + runTemplateLabel.setText("• " + descriptionTemplateEntity.getLabel()); + runTemplateLabel.setColor("116a78"); + } + + + descriptions + .forEach(datasetEntity -> { + eu.eudat.file.transformer.model.DescriptionTemplate descriptionTemplateFileModel = datasetEntity.getDescriptionTemplate(); + + // Dataset Description custom style. + XWPFParagraph datasetDescriptionParagraph = document.createParagraph(); + datasetDescriptionParagraph.setStyle("Heading4"); + datasetDescriptionParagraph.setSpacingBetween(1.5); + XWPFRun datasetDescriptionRun = datasetDescriptionParagraph.createRun(); + datasetDescriptionRun.setText("Dataset Description"); + //datasetDescriptionRun.setColor("2E75B6"); + //datasetDescriptionRun.setBold(true); + datasetDescriptionRun.setFontSize(15); + + + // Custom style for the Dataset title. + //wordBuilder.addParagraphContent("Title: " + datasetEntity.getLabel(), document, ParagraphStyle.HEADER1, BigInteger.ZERO); + XWPFParagraph datasetLabelParagraph = document.createParagraph(); +// datasetLabelParagraph.setStyle("Heading2"); + datasetLabelParagraph.setSpacingBetween(1.0); + XWPFRun runDatasetTitle1 = datasetLabelParagraph.createRun(); + runDatasetTitle1.setText("Title: "); + runDatasetTitle1.setColor("000000"); + //runDatasetTitle1.setBold(true); + //runDatasetTitle1.setFontSize(12); + XWPFRun runDatasetTitle = datasetLabelParagraph.createRun(); + runDatasetTitle.setText(datasetEntity.getLabel()); + runDatasetTitle.setColor("116a78"); + //runDatasetTitle.setBold(true); + //runDatasetTitle.setFontSize(12); + + XWPFParagraph datasetTemplateParagraph = document.createParagraph(); +// datasetTemplateParagraph.setStyle("Heading3"); + XWPFRun runDatasetTemplate1 = datasetTemplateParagraph.createRun(); + runDatasetTemplate1.setText("Template: "); + runDatasetTemplate1.setColor("000000"); + //runDatasetTemplate1.setBold(true); + //runDatasetTemplate1.setFontSize(12); + XWPFRun runDatasetTemplate = datasetTemplateParagraph.createRun(); + runDatasetTemplate.setText(descriptionTemplateFileModel.getLabel()); + runDatasetTemplate.setColor("116a78"); + //runDatasetTemplate.setBold(true); + //runDatasetTemplate.setFontSize(12); + +// /*XWPFParagraph externalReferencesParagraph = document.createParagraph(); +// externalReferencesParagraph.setStyle("Heading3"); +// XWPFRun externalReferencesRun = externalReferencesParagraph.createRun(); +// externalReferencesRun.setText("External References"); +// externalReferencesRun.setColor("2E75B6"); +// externalReferencesRun.setBold(true); +// externalReferencesRun.setFontSize(12); +// +// wordBuilder.addParagraphContent("Data Repositories", document, ParagraphStyle.HEADER4, BigInteger.ZERO); +// if (datasetEntity.getDatasetDataRepositories().size() > 0) { +// wordBuilder.addParagraphContent(datasetEntity.getDatasetDataRepositories().stream().map(DatasetDataRepository::getDataRepository).map(DataRepository::getLabel).collect(Collectors.joining(", ")) +// , document, ParagraphStyle.TEXT, BigInteger.ZERO); +// } +// wordBuilder.addParagraphContent("External Datasets", document, ParagraphStyle.HEADER4, BigInteger.ZERO); +// if (datasetEntity.getDatasetExternalDatasets().size() > 0) { +// wordBuilder.addParagraphContent(datasetEntity.getDatasetExternalDatasets().stream().map(DatasetExternalDataset::getExternalDataset).map(ExternalDataset::getLabel).collect(Collectors.joining(", ")) +// , document, ParagraphStyle.TEXT, BigInteger.ZERO); +// } +// wordBuilder.addParagraphContent("Registries", document, ParagraphStyle.HEADER4, BigInteger.ZERO); +// if (datasetEntity.getRegistries().size() > 0) { +// wordBuilder.addParagraphContent(datasetEntity.getRegistries().stream().map(Registry::getLabel).collect(Collectors.joining(", ")) +// , document, ParagraphStyle.TEXT, BigInteger.ZERO); +// } +// wordBuilder.addParagraphContent("Services", document, ParagraphStyle.HEADER4, BigInteger.ZERO); +// if (datasetEntity.getServices().size() > 0) { +// wordBuilder.addParagraphContent(datasetEntity.getServices().stream().map(DatasetService::getService).map(Service::getLabel).collect(Collectors.joining(", ")) +// , document, ParagraphStyle.TEXT, BigInteger.ZERO); +// } +// *//*wordBuilder.addParagraphContent("Tags", document, ParagraphStyle.HEADER3, BigInteger.ZERO); +// if (datasetEntity.().size() > 0) { +// wordBuilder.addParagraphContent(datasetEntity.getServices().stream().map(DatasetService::getService).map(Service::getLabel).collect(Collectors.joining(", ")) +// , document, ParagraphStyle.HEADER4, BigInteger.ZERO); +// }*/ +// +// + + XWPFParagraph datasetDescParagraph = document.createParagraph(); + XWPFRun runDatasetDescription1 = datasetDescParagraph.createRun(); + runDatasetDescription1.setText("Description: "); + runDatasetDescription1.setColor("000000"); + XWPFRun runDatasetDescription = datasetDescParagraph.createRun(); + runDatasetDescription.setText(descriptionTemplateFileModel.getLabel()); + runDatasetDescription.setColor("116a78"); + //wordBuilder.addParagraphContent(datasetEntity.getDescription(), document, ParagraphStyle.HTML, BigInteger.ZERO, 0); + + document.createParagraph(); + + + try { + PagedDatasetProfile pagedDatasetProfile = DescriptionTemplateService.getPagedProfile(datasetEntity); + + visibilityRuleService.setProperties(datasetEntity.getProperties()); + visibilityRuleService.buildVisibilityContext(pagedDatasetProfile.getRules()); + //pagedDatasetProfile = this.objectMapper.readValue(this.objectMapper.writeValueAsString(pagedDatasetProfile), eu.eudat.file.transformer.model.descriptiontemplatedefinition.Definition.class); + + wordBuilder.build(document, pagedDatasetProfile, visibilityRuleService); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + // Page break at the end of the Dataset. + XWPFParagraph parBreakDataset = document.createParagraph(); + parBreakDataset.setPageBreak(true); + }); + } + } + +// // Removes the top empty headings. +// for (int i = 0; i < 6; i++) { +// document.removeBodyElement(0); +// } + + + if(powered_pos != -1) { + document.getLastParagraph().setPageBreak(false); + document.createParagraph(); + document.setParagraph(powered_par, document.getParagraphs().size() - 1); + + document.createParagraph(); + document.setParagraph(argos_img_par, document.getParagraphs().size() - 1); + + document.removeBodyElement(powered_pos + 1); + document.removeBodyElement(powered_pos + 1); + } + + wordBuilder.fillFooter(dmpEntity, null, document, false); + + String fileName; + if (!grants.isEmpty() && grants.get(0).getLabel() != null) { + fileName = "DMP_" + grants.get(0).getLabel(); + } + else { + fileName = "DMP_" + dmpEntity.getLabel(); + } + if (versioned) { + fileName += "_" + dmpEntity.getVersion(); + } + // fileName = fileName.replaceAll("[^a-zA-Z0-9+ ]", ""); + FileEnvelopeInternal exportEnvelope = new FileEnvelopeInternal(); + exportEnvelope.setFilename(fileName + ".docx"); + String uuid = UUID.randomUUID().toString(); + File exportFile = new File(fileStorageProperties.getTemp() + "\\" + uuid + ".docx"); + FileOutputStream out = new FileOutputStream(exportFile); + document.write(out); + out.close(); + exportEnvelope.setFile(exportFile); + + return exportEnvelope; + } + + private FileEnvelope getDescriptionWordDocument(DescriptionFileTransformerModel descriptionFileTransformerModel) throws IOException { + FileEnvelopeInternal wordFile = getDescriptionWordDocumentInternal(descriptionFileTransformerModel); + FileEnvelope fileEnvelope = new FileEnvelope(); + fileEnvelope.setFilename(wordFile.getFilename()); + try (FileInputStream fis = new FileInputStream(wordFile.getFile())) { + fileEnvelope.setFile(fis.readAllBytes()); + } + return fileEnvelope; + } + + private FileEnvelopeInternal getDescriptionWordDocumentInternal(DescriptionFileTransformerModel descriptionFileTransformerModelEntityEntity) throws IOException { + WordBuilder wordBuilder = new WordBuilder(fileTemplateProperties, fileStorageProperties); + DmpFileTransformerModel dmpEntity = descriptionFileTransformerModelEntityEntity.getDmp(); + if (dmpEntity == null) { + throw new IllegalArgumentException("Dmp is invalid"); + } + XWPFDocument document = new XWPFDocument(new FileInputStream(ResourceUtils.getFile(fileTemplateProperties.getWordDescriptionTemplate()))); + VisibilityRuleService visibilityRuleService = new VisibilityRuleServiceImpl(); + + wordBuilder.fillFirstPage(dmpEntity, descriptionFileTransformerModelEntityEntity, document, true); + wordBuilder.fillFooter(dmpEntity, descriptionFileTransformerModelEntityEntity, document, true); + + int powered_pos = wordBuilder.findPosOfPoweredBy(document); + XWPFParagraph powered_par = null; + XWPFParagraph argos_img_par = null; + if(powered_pos != -1) { + powered_par = document.getParagraphArray(powered_pos); + argos_img_par = document.getParagraphArray(powered_pos + 1); + } + +// wordBuilder.addParagraphContent(datasetEntity.getLabel(), document, ParagraphStyle.HEADER1, BigInteger.ZERO); + + // Space below Dataset title. +// XWPFParagraph parBreakDataset = document.createParagraph(); +// +// XWPFParagraph datasetTemplateParagraph = document.createParagraph(); +// datasetTemplateParagraph.setStyle("Heading2"); +// XWPFRun runDatasetTemplate1 = datasetTemplateParagraph.createRun(); +// runDatasetTemplate1.setText("Template: "); +// runDatasetTemplate1.setBold(true); +// runDatasetTemplate1.setFontSize(12); +// XWPFRun runDatasetTemplate = datasetTemplateParagraph.createRun(); +// runDatasetTemplate.setText(datasetEntity.getProfile().getLabel()); +// runDatasetTemplate.setColor("2E75B6"); +// runDatasetTemplate.setBold(true); +// runDatasetTemplate.setFontSize(12); +// +// wordBuilder.addParagraphContent(datasetEntity.getDescription(), document, ParagraphStyle.HTML, BigInteger.ZERO); + + /*XWPFParagraph externalReferencesParagraph = document.createParagraph(); + externalReferencesParagraph.setStyle("Heading2"); + XWPFRun externalReferencesRun = externalReferencesParagraph.createRun(); + externalReferencesRun.setText("External References"); + externalReferencesRun.setColor("2E75B6"); + externalReferencesRun.setBold(true); + externalReferencesRun.setFontSize(12); + + wordBuilder.addParagraphContent("Data Repositories", document, ParagraphStyle.HEADER3, BigInteger.ZERO); + if (datasetEntity.getDatasetDataRepositories().size() > 0) { + wordBuilder.addParagraphContent(datasetEntity.getDatasetDataRepositories().stream().map(DatasetDataRepository::getDataRepository).map(DataRepository::getLabel).collect(Collectors.joining(", ")) + , document, ParagraphStyle.TEXT, BigInteger.ZERO); + } + wordBuilder.addParagraphContent("External Datasets", document, ParagraphStyle.HEADER3, BigInteger.ZERO); + if (datasetEntity.getDatasetExternalDatasets().size() > 0) { + wordBuilder.addParagraphContent(datasetEntity.getDatasetExternalDatasets().stream().map(DatasetExternalDataset::getExternalDataset).map(ExternalDataset::getLabel).collect(Collectors.joining(", ")) + , document, ParagraphStyle.TEXT, BigInteger.ZERO); + } + wordBuilder.addParagraphContent("Registries", document, ParagraphStyle.HEADER3, BigInteger.ZERO); + if (datasetEntity.getRegistries().size() > 0) { + wordBuilder.addParagraphContent(datasetEntity.getRegistries().stream().map(Registry::getLabel).collect(Collectors.joining(", ")) + , document, ParagraphStyle.TEXT, BigInteger.ZERO); + } + wordBuilder.addParagraphContent("Services", document, ParagraphStyle.HEADER3, BigInteger.ZERO); + if (datasetEntity.getServices().size() > 0) { + wordBuilder.addParagraphContent(datasetEntity.getServices().stream().map(DatasetService::getService).map(Service::getLabel).collect(Collectors.joining(", ")) + , document, ParagraphStyle.TEXT, BigInteger.ZERO); + }*/ + /*wordBuilder.addParagraphContent("Tags", document, ParagraphStyle.HEADER3, BigInteger.ZERO); + if (datasetEntity.().size() > 0) { + wordBuilder.addParagraphContent(datasetEntity.getServices().stream().map(DatasetService::getService).map(Service::getLabel).collect(Collectors.joining(", ")) + , document, ParagraphStyle.HEADER4, BigInteger.ZERO); + }*/ + +// wordBuilder.addParagraphContent("Dataset Description", document, ParagraphStyle.HEADER2, BigInteger.ZERO); + PagedDatasetProfile pagedDatasetProfile = DescriptionTemplateService.getPagedProfile(descriptionFileTransformerModelEntityEntity); + visibilityRuleService.setProperties(descriptionFileTransformerModelEntityEntity.getProperties()); + visibilityRuleService.buildVisibilityContext(pagedDatasetProfile.getRules()); + wordBuilder.build(document, pagedDatasetProfile, visibilityRuleService); + String label = descriptionFileTransformerModelEntityEntity.getLabel().replaceAll("[^a-zA-Z0-9+ ]", ""); +// File exportFile = new File(label + ".docx"); + + // Removes the top empty headings. +// for (int i = 0; i < 6; i++) { +// document.removeBodyElement(0); +// } + + if(powered_pos != -1) { + document.getLastParagraph().setPageBreak(false); + document.createParagraph(); + document.setParagraph(powered_par, document.getParagraphs().size() - 1); + + document.createParagraph(); + document.setParagraph(argos_img_par, document.getParagraphs().size() - 1); + + document.removeBodyElement(powered_pos + 1); + document.removeBodyElement(powered_pos + 1); + } + + label = descriptionFileTransformerModelEntityEntity.getLabel().replaceAll("[^a-zA-Z0-9+ ]", ""); + FileEnvelopeInternal exportEnvelope = new FileEnvelopeInternal(); + exportEnvelope.setFilename(label + ".docx"); + String uuid = UUID.randomUUID().toString(); + File exportFile = new File(fileStorageProperties.getTemp() + uuid + ".docx"); + FileOutputStream out = new FileOutputStream(exportFile); + document.write(out); + out.close(); + exportEnvelope.setFile(exportFile); + + return exportEnvelope; + //FileOutputStream out = new FileOutputStream(exportFile); + // document.write(out); + // out.close(); + // return exportFile; + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/model/PidLink.java b/core/src/main/java/eu/eudat/file/transformer/model/PidLink.java new file mode 100644 index 0000000..a3ae1ee --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/model/PidLink.java @@ -0,0 +1,22 @@ +package eu.eudat.file.transformer.model; + +public class PidLink { + private String pid; + private String link; + + public String getPid() { + return pid; + } + + public void setPid(String pid) { + this.pid = pid; + } + + public String getLink() { + return link; + } + + public void setLink(String link) { + this.link = link; + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/model/enums/FileFormats.java b/core/src/main/java/eu/eudat/file/transformer/model/enums/FileFormats.java new file mode 100644 index 0000000..c54d93a --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/model/enums/FileFormats.java @@ -0,0 +1,32 @@ +package eu.eudat.file.transformer.model.enums; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import eu.eudat.file.transformer.entities.descriptiontemplate.fielddata.AutoCompleteDataEntity; +import eu.eudat.file.transformer.enums.DatabaseEnum; +import eu.eudat.file.transformer.enums.EnumUtils; + +import java.util.Map; + +public enum FileFormats implements DatabaseEnum { + DOCX("docx"), + PDF("pdf"); + + private final String value; + + FileFormats(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + private static final Map map = EnumUtils.getEnumValueMap(FileFormats.class); + + @JsonCreator + public static FileFormats of(String i) { + return map.get(i); + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/model/file/FileEnvelopeInternal.java b/core/src/main/java/eu/eudat/file/transformer/model/file/FileEnvelopeInternal.java new file mode 100644 index 0000000..c3b2501 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/model/file/FileEnvelopeInternal.java @@ -0,0 +1,28 @@ +package eu.eudat.file.transformer.model.file; + +import java.io.File; + +/** + * Created by ikalyvas on 3/6/2018. + */ + +public class FileEnvelopeInternal { + private String filename; + private File file; + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public File getFile() { + return file; + } + + public void setFile(File file) { + this.file = file; + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/model/visibility/Rule.java b/core/src/main/java/eu/eudat/file/transformer/model/visibility/Rule.java new file mode 100644 index 0000000..82906ea --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/model/visibility/Rule.java @@ -0,0 +1,40 @@ +package eu.eudat.file.transformer.model.visibility; +public class Rule { + private String sourceField; + private String targetField; + private String requiredValue; + private String type; + + public String getSourceField() { + return sourceField; + } + + public void setSourceField(String sourceField) { + this.sourceField = sourceField; + } + + public String getTargetField() { + return targetField; + } + + public void setTargetField(String targetField) { + this.targetField = targetField; + } + + public String getRequiredValue() { + return requiredValue; + } + + public void setRequiredValue(String requiredValue) { + this.requiredValue = requiredValue; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + +} diff --git a/core/src/main/java/eu/eudat/file/transformer/model/visibility/RuleMapper.java b/core/src/main/java/eu/eudat/file/transformer/model/visibility/RuleMapper.java new file mode 100644 index 0000000..bf97a2c --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/model/visibility/RuleMapper.java @@ -0,0 +1,53 @@ +package eu.eudat.file.transformer.model.visibility; + +import eu.eudat.file.transformer.model.descriptiontemplatedefinition.Definition; +import eu.eudat.file.transformer.model.descriptiontemplatedefinition.Field; +import eu.eudat.file.transformer.model.descriptiontemplatedefinition.FieldSet; +import eu.eudat.file.transformer.model.descriptiontemplatedefinition.Section; +import eu.eudat.file.transformer.model.descriptiontemplatedefinition.fielddata.BaseFieldData; + +import java.util.ArrayList; +import java.util.List; + +public class RuleMapper { + + public static List mapRulesFromDefinition(Definition definitionFileModel) { + return definitionFileModel.getSections().stream().flatMap(section -> mapRulesFromSection(section).stream()).toList(); + } + + private static List mapRulesFromSection(Section section) { + List rules = new ArrayList<>(); + if (!section.getSections().isEmpty()) { + rules.addAll(section.getSections().stream().flatMap(section1 -> mapRulesFromSection(section1).stream()).toList()); + } + if (!section.getFieldSets().isEmpty()) { + rules.addAll(section.getFieldSets().stream().flatMap(fieldSet -> mapRuleFromFieldSet(fieldSet).stream()).toList()); + } + + return rules; + } + + private static List mapRuleFromFieldSet(FieldSet fieldSet) { + List rules = new ArrayList<>(); + if (!fieldSet.getFields().isEmpty()) { + rules.addAll(fieldSet.getFields().stream().flatMap(field -> mapRules(field).stream()).toList()); + } + + return rules; + } + + private static List mapRules(Field field) { + List rules = new ArrayList<>(); + BaseFieldData data = field.getData(); + field.getVisibilityRules().forEach(visibilityRule -> { + Rule rule = new Rule(); + rule.setSourceField(field.getId()); + rule.setType(data.getFieldType().getValue()); + rule.setRequiredValue(visibilityRule.getValue()); + rule.setTargetField(visibilityRule.getTarget()); + rules.add(rule); + }); + + return rules; + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityContext.java b/core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityContext.java new file mode 100644 index 0000000..a31239b --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityContext.java @@ -0,0 +1,44 @@ +package eu.eudat.file.transformer.services.visibility; +import eu.eudat.file.transformer.entities.user.components.commons.Rule; + +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +/** + * Created by ikalyvas on 3/5/2018. + */ +public class VisibilityContext { + private List visibilityRules = new LinkedList<>(); + + public List getVisibilityRules() { + return visibilityRules; + } + + public VisibilityRule get(String id) { + Optional rule = visibilityRules.stream().filter(item -> item.getVisibilityRuleTargetId().equals(id)).findFirst(); + if (rule.isPresent()) return rule.get(); + return null; + } + + public void buildVisibilityContext(List sources) { + sources.forEach(this::addToVisibilityRulesContext); + } + + private void addToVisibilityRulesContext(Rule item) { + VisibilityRuleSource source = new VisibilityRuleSource(); + source.setVisibilityRuleSourceId(item.getSourceField()); + source.setVisibilityRuleSourceValue(item.getRequiredValue()); + + Optional visibilityRuleOptional = visibilityRules.stream().filter(rule -> rule.getVisibilityRuleTargetId().equals(item.getTargetField())).findFirst(); + if (visibilityRuleOptional.isPresent()) visibilityRuleOptional.get().getVisibilityRuleSources().add(source); + else { + List sources = new LinkedList<>(); + sources.add(source); + VisibilityRule visibilityRule = new VisibilityRule(); + visibilityRule.setVisibilityRuleTargetId(item.getTargetField()); + visibilityRule.setVisibilityRuleSources(sources); + this.visibilityRules.add(visibilityRule); + } + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRule.java b/core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRule.java new file mode 100644 index 0000000..223ebf7 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRule.java @@ -0,0 +1,27 @@ +package eu.eudat.file.transformer.services.visibility; + +import java.util.List; + +/** + * Created by ikalyvas on 3/5/2018. + */ +public class VisibilityRule { + private String visibilityRuleTargetId; + private List visibilityRuleSources; + + public String getVisibilityRuleTargetId() { + return visibilityRuleTargetId; + } + + public void setVisibilityRuleTargetId(String visibilityRuleTargetId) { + this.visibilityRuleTargetId = visibilityRuleTargetId; + } + + public List getVisibilityRuleSources() { + return visibilityRuleSources; + } + + public void setVisibilityRuleSources(List visibilityRuleSources) { + this.visibilityRuleSources = visibilityRuleSources; + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRuleService.java b/core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRuleService.java new file mode 100644 index 0000000..ae76002 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRuleService.java @@ -0,0 +1,17 @@ +package eu.eudat.file.transformer.services.visibility; + +import eu.eudat.file.transformer.entities.user.components.commons.Rule; +import eu.eudat.file.transformer.model.descriptionproperties.PropertyDefinition; + +import java.util.List; + +/** + * Created by ikalyvas on 3/5/2018. + */ +public interface VisibilityRuleService { + boolean isElementVisible(String id); + + void buildVisibilityContext(List sources); + + void setProperties(PropertyDefinition properties); +} diff --git a/core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRuleServiceImpl.java b/core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRuleServiceImpl.java new file mode 100644 index 0000000..bbbb8f8 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRuleServiceImpl.java @@ -0,0 +1,73 @@ +package eu.eudat.file.transformer.services.visibility; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.eudat.file.transformer.entities.user.components.commons.Rule; +import eu.eudat.file.transformer.model.descriptionproperties.Field; +import eu.eudat.file.transformer.model.descriptionproperties.PropertyDefinition; +import java.util.*; + +/** + * Created by ikalyvas on 3/5/2018. + */ +public class VisibilityRuleServiceImpl implements VisibilityRuleService { + private final Map elementVisibility = new HashMap<>(); + private PropertyDefinition properties; + private ObjectMapper mapper; + + public VisibilityRuleServiceImpl() { + this.mapper = new ObjectMapper(); + } + + public boolean isElementVisible(String id) { + return !this.elementVisibility.containsKey(id) || this.elementVisibility.get(id); + } + + public void setProperties(PropertyDefinition properties) { + this.properties = properties; + /*this.properties.entrySet().stream() + .filter(stringObjectEntry -> stringObjectEntry.getValue() instanceof String && ((String) stringObjectEntry.getValue()).startsWith("[") + && ((String) stringObjectEntry.getValue()).endsWith("]")) + .forEach(stringObjectEntry -> stringObjectEntry.setValue(parseArray((String) stringObjectEntry.getValue())));*/ + } + + private List parseArray(String original) { + String parsed = original.replace("[", "").replace("\"", "").replace("]", ""); + return Arrays.asList(parsed.split(",")); + } + + public void buildVisibilityContext(List sources) { + VisibilityContext visibilityContext = new VisibilityContext(); + visibilityContext.buildVisibilityContext(sources); + visibilityContext.getVisibilityRules().forEach(this::evaluateVisibility); + } + + private void evaluateVisibility(VisibilityRule rule) { + List sources = rule.getVisibilityRuleSources(); + for(VisibilityRuleSource source: sources){ + Field fieldFileModel = properties.getFields().stream().filter(fieldFileModel1 -> fieldFileModel1.getKey().equals(source.getVisibilityRuleSourceId())).findFirst().orElse(null); + if (fieldFileModel != null + && isContained(fieldFileModel, source.getVisibilityRuleSourceValue())) { + this.elementVisibility.put(rule.getVisibilityRuleTargetId(), true); + } else { + this.elementVisibility.put(rule.getVisibilityRuleTargetId(), + this.elementVisibility.getOrDefault(rule.getVisibilityRuleTargetId(), false)); + } + } + } + + private Boolean isContained(Field values, String source) { + try { + Object value = mapper.readValue(values.getValue(), Object.class); + if (value instanceof Collection) { + return ((Collection) value).contains(source); + } else { + if (value != null) { + return value.toString().equals(source); + } + return false; + } + } catch (JsonProcessingException e) { + return false; + } + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRuleSource.java b/core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRuleSource.java new file mode 100644 index 0000000..4d4489f --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/services/visibility/VisibilityRuleSource.java @@ -0,0 +1,25 @@ +package eu.eudat.file.transformer.services.visibility; + +/** + * Created by ikalyvas on 3/5/2018. + */ +public class VisibilityRuleSource { + private String visibilityRuleSourceId; + private String visibilityRuleSourceValue; + + public String getVisibilityRuleSourceId() { + return visibilityRuleSourceId; + } + + public void setVisibilityRuleSourceId(String visibilityRuleSourceId) { + this.visibilityRuleSourceId = visibilityRuleSourceId; + } + + public String getVisibilityRuleSourceValue() { + return visibilityRuleSourceValue; + } + + public void setVisibilityRuleSourceValue(String visibilityRuleSourceValue) { + this.visibilityRuleSourceValue = visibilityRuleSourceValue; + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/descriptionTemplate/DescriptionTemplateService.java b/core/src/main/java/eu/eudat/file/transformer/utils/descriptionTemplate/DescriptionTemplateService.java new file mode 100644 index 0000000..49d3d25 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/descriptionTemplate/DescriptionTemplateService.java @@ -0,0 +1,42 @@ +package eu.eudat.file.transformer.utils.descriptionTemplate; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import eu.eudat.file.transformer.entities.descriptiontemplate.DefinitionEntity; +import eu.eudat.file.transformer.entities.user.composite.DatasetProfile; +import eu.eudat.file.transformer.entities.user.composite.PagedDatasetProfile; +import eu.eudat.file.transformer.entities.xml.XmlBuilder; +import eu.eudat.file.transformer.model.DescriptionFileTransformerModel; +import eu.eudat.file.transformer.model.DescriptionTemplate; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.HashMap; +import java.util.Map; + +public class DescriptionTemplateService { + private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final XmlMapper xmlMapper = new XmlMapper(); + + public static PagedDatasetProfile getPagedProfile(DescriptionFileTransformerModel dataset) throws JsonProcessingException { + DatasetProfile datasetprofile = generateDatasetProfileModel(dataset.getDescriptionTemplate()); + datasetprofile.setStatus(dataset.getStatus().getValue()); + if (dataset.getProperties() != null) { + //Map properties = objectMapper.convertValue(dataset.getProperties().getFields(), HashMap.class); + datasetprofile.fromJsonObject(dataset.getProperties().getFields()); + } + PagedDatasetProfile pagedDatasetProfile = new PagedDatasetProfile(); + pagedDatasetProfile.buildPagedDatasetProfile(datasetprofile); + return pagedDatasetProfile; + } + + public static DatasetProfile generateDatasetProfileModel(DescriptionTemplate profile) { + DefinitionEntity viewstyle = new DefinitionEntity().fromDefinition(profile.getDefinition()); + + DatasetProfile datasetprofile = new DatasetProfile(); + datasetprofile.buildProfile(viewstyle); + + return datasetprofile; + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/interfaces/Applier.java b/core/src/main/java/eu/eudat/file/transformer/utils/interfaces/Applier.java new file mode 100644 index 0000000..a955bec --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/interfaces/Applier.java @@ -0,0 +1,8 @@ +package eu.eudat.file.transformer.utils.interfaces; + +/** + * Created by ikalyvas on 3/1/2018. + */ +public interface Applier { + void apply(A applier, V value); +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/interfaces/ApplierWithValue.java b/core/src/main/java/eu/eudat/file/transformer/utils/interfaces/ApplierWithValue.java new file mode 100644 index 0000000..d14f7ee --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/interfaces/ApplierWithValue.java @@ -0,0 +1,8 @@ +package eu.eudat.file.transformer.utils.interfaces; + +/** + * Created by ikalyvas on 2/27/2018. + */ +public interface ApplierWithValue { + R apply(A applier, V value); +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/interfaces/Cloneable.java b/core/src/main/java/eu/eudat/file/transformer/utils/interfaces/Cloneable.java new file mode 100644 index 0000000..5746c95 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/interfaces/Cloneable.java @@ -0,0 +1,8 @@ +package eu.eudat.file.transformer.utils.interfaces; + +/** + * Created by ikalyvas on 2/5/2018. + */ +public interface Cloneable { + T clone(); +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/interfaces/ModelSerializer.java b/core/src/main/java/eu/eudat/file/transformer/utils/interfaces/ModelSerializer.java new file mode 100644 index 0000000..2e34cc1 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/interfaces/ModelSerializer.java @@ -0,0 +1,8 @@ +package eu.eudat.file.transformer.utils.interfaces; + + +import eu.eudat.file.transformer.entities.common.DatabaseModelDefinition; + +public interface ModelSerializer { + void fromDatabaseDefinition(T viewStyle, U model); +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/json/JavaToJson.java b/core/src/main/java/eu/eudat/file/transformer/utils/json/JavaToJson.java new file mode 100644 index 0000000..b75f290 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/json/JavaToJson.java @@ -0,0 +1,13 @@ +package eu.eudat.file.transformer.utils.json; + +public class JavaToJson { + + public static String objectStringToJson(String object) { + String result = object.replaceAll("=", "\":\"") + .replaceAll("\\{", "{\"") + .replaceAll(", ", "\", \"") + .replaceAll("}", "\"}" ) + .replaceAll("}\", \"\\{", "}, {"); + return result; + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/json/JsonSearcher.java b/core/src/main/java/eu/eudat/file/transformer/utils/json/JsonSearcher.java new file mode 100644 index 0000000..a5e33e6 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/json/JsonSearcher.java @@ -0,0 +1,81 @@ +package eu.eudat.file.transformer.utils.json; + +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +public class JsonSearcher { + + public static List findNodes(JsonNode root, String key, String value) { + return findNodes(root, key, value, false); + } + + public static List findNodes(JsonNode root, String key, String value, boolean parent) { + List nodes = new ArrayList<>(); + for (Iterator it = root.elements(); it.hasNext(); ) { + JsonNode node = it.next(); + int found = 0; + for (Iterator iter = node.fieldNames(); iter.hasNext(); ) { + String fieldName = iter.next(); + if (fieldName.equals(key)) { + if (node.get(fieldName).asText().equals(value) || node.get(fieldName).asText().startsWith(value)) { + if (parent) { + nodes.add(root); + } else { + nodes.add(node); + } + found++; + } + else if(node.get(fieldName).isArray()){ + for(JsonNode item: node.get(fieldName)){ + if(item.asText().equals(value) || item.asText().startsWith(value)){ + if (parent) { + nodes.add(root); + } else { + nodes.add(node); + } + found++; + } + } + } + } + + } + if (found == 0) { + nodes.addAll(findNodes(node, key, value, parent)); + } + } + return nodes; + } + + public static List getParentValues(JsonNode root, String childValue, String key) { + List values = new LinkedList<>(); + + for (Iterator it = root.elements(); it.hasNext(); ) { + JsonNode node = it.next(); + int found = 0; + for (Iterator iter = node.fieldNames(); iter.hasNext(); ) { + String fieldName = iter.next(); + if (fieldName.equals(key)) { + if (node.get(fieldName).asText().equals(childValue) || node.get(fieldName).asText().startsWith(childValue)) { + values.add(childValue); + found++; + } + } + + } + if (found == 0) { + values.addAll(getParentValues(node, childValue, key)); + if (!values.isEmpty() && node.has(key)) { + values.add(node.get(key).asText()); + values.remove(childValue); + } + } + } + + return values; + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/json/MultiDateDeserializer.java b/core/src/main/java/eu/eudat/file/transformer/utils/json/MultiDateDeserializer.java new file mode 100644 index 0000000..aa82f7d --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/json/MultiDateDeserializer.java @@ -0,0 +1,41 @@ +package eu.eudat.file.transformer.utils.json; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +public class MultiDateDeserializer extends StdDeserializer { + + private static final List DATE_FORMATS = Arrays.asList("yyyy-MM-dd'T'HH:mm:ss'Z'", "yyyy-MM-dd'T'HH:mm:ss.S"); + + + public MultiDateDeserializer() { + super(Date.class); + } + + protected MultiDateDeserializer(Class vc) { + super(vc); + } + + @Override + public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { + String text = p.getText(); + + for (String dateFormat: DATE_FORMATS) { + try { + return new SimpleDateFormat(dateFormat).parse(text); + } catch (ParseException ignored) { + } + } + throw new JsonParseException(p, "No supported Date format"); + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/pdf/PDFUtils.java b/core/src/main/java/eu/eudat/file/transformer/utils/pdf/PDFUtils.java new file mode 100644 index 0000000..ccefd6f --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/pdf/PDFUtils.java @@ -0,0 +1,36 @@ +package eu.eudat.file.transformer.utils.pdf; + +import eu.eudat.file.transformer.model.file.FileEnvelopeInternal; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.util.UUID; + +public class PDFUtils { + + public static File convertToPDF(FileEnvelopeInternal file, String tempPath, String pdfUrl) throws IOException { + WebClient webClient = WebClient.builder().baseUrl(pdfUrl).build(); + + + byte[] queueResult = webClient.post().uri("forms/libreoffice/convert") + .headers(httpHeaders -> httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA)) + .body(BodyInserters.fromMultipartData("file", new FileSystemResource(file.getFile()))) + .retrieve().bodyToMono(byte[].class).block(); + + File resultPdf = new File(tempPath + "/" + UUID.randomUUID() + ".pdf"); + if (queueResult != null) { + try (FileOutputStream output = new FileOutputStream(resultPdf)) { + output.write(queueResult); + } + } + Files.deleteIfExists(file.getFile().toPath()); + + return resultPdf; + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/pid/PidLoader.java b/core/src/main/java/eu/eudat/file/transformer/utils/pid/PidLoader.java new file mode 100644 index 0000000..b6f74f8 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/pid/PidLoader.java @@ -0,0 +1,36 @@ +package eu.eudat.file.transformer.utils.pid; + +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.eudat.file.transformer.model.PidLink; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.ResourceUtils; + +import java.io.IOException; +import java.util.List; + +public class PidLoader { + private static final Logger logger = LoggerFactory.getLogger(PidLoader.class); + private static final ObjectMapper objectMapper = new ObjectMapper(); + + public static List loadPidLinks(String pidPath) { + try { + return objectMapper.readValue(ResourceUtils.getFile(pidPath), PidLinksWrapper.class).getPidLinks(); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + return null; + } + + protected class PidLinksWrapper { + private List pidLinks; + + public List getPidLinks() { + return pidLinks; + } + + public void setPidLinks(List pidLinks) { + this.pidLinks = pidLinks; + } + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/types/ParagraphStyle.java b/core/src/main/java/eu/eudat/file/transformer/utils/types/ParagraphStyle.java new file mode 100644 index 0000000..6551ffe --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/types/ParagraphStyle.java @@ -0,0 +1,49 @@ +package eu.eudat.file.transformer.utils.types; + +/** + * Created by ikalyvas on 2/26/2018. + */ +public enum ParagraphStyle { + TEXT(0), HEADER1(1), HEADER2(2), HEADER3(3), HEADER4(4), TITLE(5), FOOTER(6), COMMENT(7), HEADER5(8), HEADER6(9), HTML(10), IMAGE(11); + + private Integer value; + + private ParagraphStyle(Integer value) { + this.value = value; + } + + public Integer getValue() { + return value; + } + + public static ParagraphStyle fromInteger(Integer value) { + switch (value) { + case 0: + return TEXT; + case 1: + return HEADER1; + case 2: + return HEADER2; + case 3: + return HEADER3; + case 4: + return HEADER4; + case 5: + return TITLE; + case 6: + return FOOTER; + case 7: + return COMMENT; + case 8: + return HEADER5; + case 9: + return HEADER6; + case 10: + return HTML; + case 11: + return IMAGE; + default: + throw new RuntimeException("Unsupported ParagraphStyle Code"); + } + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/types/TextStyle.java b/core/src/main/java/eu/eudat/file/transformer/utils/types/TextStyle.java new file mode 100644 index 0000000..6bc8400 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/types/TextStyle.java @@ -0,0 +1,31 @@ +package eu.eudat.file.transformer.utils.types; + +/** + * Created by ikalyvas on 2/27/2018. + */ +public enum TextStyle { + ITALIC(0), BOLD(1), CAPS(2); + + private Integer value; + + private TextStyle(Integer value) { + this.value = value; + } + + public Integer getValue() { + return value; + } + + public static TextStyle fromInteger(Integer value) { + switch (value) { + case 0: + return ITALIC; + case 1: + return BOLD; + case 2: + return CAPS; + default: + throw new RuntimeException("Unsupported TextStyle Code"); + } + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/word/HtmlToWorldBuilder.java b/core/src/main/java/eu/eudat/file/transformer/utils/word/HtmlToWorldBuilder.java new file mode 100644 index 0000000..d844d61 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/word/HtmlToWorldBuilder.java @@ -0,0 +1,289 @@ +package eu.eudat.file.transformer.utils.word; + +import org.apache.poi.xwpf.usermodel.*; +import org.apache.xmlbeans.XmlCursor; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; +import org.jsoup.select.NodeTraversor; +import org.jsoup.select.NodeVisitor; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.*; + +import java.math.BigInteger; +import java.util.*; + +public class HtmlToWorldBuilder implements NodeVisitor { + + private final Map properties = new LinkedHashMap<>(); + private XWPFParagraph paragraph; + private XWPFRun run; + private Boolean dumpRun; + private final float indentation; + private Boolean isIdentationUsed; + private XWPFNumbering numbering; + private Queue abstractNumId; + private BigInteger numberingLevel; + private XmlCursor cursor; + + public static HtmlToWorldBuilder convertInTable(XWPFTableCell document, Document htmlDocument, float indentation) { + XWPFParagraph paragraph = document.addParagraph(); + paragraph.setIndentFromLeft(Math.round(400 * indentation)); + HtmlToWorldBuilder htmlToWorldBuilder = new HtmlToWorldBuilder(paragraph, indentation, null); + NodeTraversor.traverse(htmlToWorldBuilder, htmlDocument); + return htmlToWorldBuilder; + } + + public static HtmlToWorldBuilder convert(XWPFDocument document, Document htmlDocument, float indentation) { + XWPFParagraph paragraph = document.createParagraph(); + paragraph.setIndentFromLeft(Math.round(400 * indentation)); + HtmlToWorldBuilder htmlToWorldBuilder = new HtmlToWorldBuilder(paragraph, indentation, null); + NodeTraversor.traverse(htmlToWorldBuilder, htmlDocument); + return htmlToWorldBuilder; + } + + public HtmlToWorldBuilder(XWPFParagraph paragraph, float indentation, XmlCursor cursor) { + this.paragraph = paragraph; + this.run = this.paragraph.createRun(); + this.dumpRun = false; + this.indentation = indentation; + this.isIdentationUsed = false; + this.run.setFontSize(11); + this.abstractNumId = new ArrayDeque<>(); + this.numberingLevel = BigInteger.valueOf(-1); + this.setDefaultIndentation(); + this.cursor = cursor; + } + + @Override + public void head(Node node, int i) { + String name = node.nodeName(); + if (name.equals("#text")) { + String text = ((TextNode)node).text(); + this.run.setText(text); + this.dumpRun = true; + } else { + properties.put(name, true); + } + if (dumpRun) { + this.run = this.paragraph.createRun(); + this.run.setFontSize(11); + this.dumpRun = false; + } + parseProperties(node); + properties.clear(); + } + + private void parseProperties(Node node) { + properties.entrySet().forEach(stringBooleanEntry -> { + switch (stringBooleanEntry.getKey()) { + case "i" : + case "em": + this.run.setItalic(stringBooleanEntry.getValue()); + break; + case "b": + case "strong": + this.run.setBold(stringBooleanEntry.getValue()); + break; + case "u": + case "ins": + this.run.setUnderline(stringBooleanEntry.getValue() ? UnderlinePatterns.SINGLE : UnderlinePatterns.NONE); + break; + case "small": + this.run.setFontSize(stringBooleanEntry.getValue() ? 8 : 11); + break; + case "del": + case "strike": + case "strikethrough": + case "s": + this.run.setStrikeThrough(stringBooleanEntry.getValue()); + break; + case "mark": + this.run.setTextHighlightColor(stringBooleanEntry.getValue() ? STHighlightColor.YELLOW.toString() : STHighlightColor.NONE.toString()); + break; + case "sub": + this.run.setSubscript(stringBooleanEntry.getValue() ? VerticalAlign.SUBSCRIPT : VerticalAlign.BASELINE); + break; + case "sup": + this.run.setSubscript(stringBooleanEntry.getValue() ? VerticalAlign.SUPERSCRIPT : VerticalAlign.BASELINE); + break; + case "div": + case "p": + if(this.cursor != null) { + this.paragraph = this.paragraph.getDocument().insertNewParagraph(this.cursor); + this.cursor = this.paragraph.getCTP().newCursor(); + this.cursor.toNextSibling(); + } else { + this.paragraph = this.paragraph.getDocument().createParagraph(); + } + this.run = this.paragraph.createRun(); + this.isIdentationUsed = false; + this.setDefaultIndentation(); + if (stringBooleanEntry.getValue()) { + if (node.hasAttr("align")) { + String alignment = node.attr("align"); + this.paragraph.setAlignment(ParagraphAlignment.valueOf(alignment.toUpperCase(Locale.ROOT))); + } + } + break; + case "blockquote": + if(this.cursor != null) { + this.paragraph = this.paragraph.getDocument().insertNewParagraph(this.cursor); + this.cursor = this.paragraph.getCTP().newCursor(); + } else { + this.paragraph = this.paragraph.getDocument().createParagraph(); + } + this.run = this.paragraph.createRun(); + if (stringBooleanEntry.getValue()) { + this.paragraph.setIndentationLeft(400); + } else { + this.isIdentationUsed = false; + this.setDefaultIndentation(); + } + break; + case "ul": + if (stringBooleanEntry.getValue()) { + createNumbering(STNumberFormat.BULLET); + } else { + if(this.cursor != null) { + this.paragraph = this.paragraph.getDocument().insertNewParagraph(this.cursor); + this.cursor = this.paragraph.getCTP().newCursor(); + } else { + this.paragraph = this.paragraph.getDocument().createParagraph(); + } + this.run = this.paragraph.createRun(); + this.isIdentationUsed = false; + this.setDefaultIndentation(); + this.numberingLevel = this.numberingLevel.subtract(BigInteger.ONE); + ((ArrayDeque)this.abstractNumId).removeLast(); + } + break; + case "ol": + if (stringBooleanEntry.getValue()) { + createNumbering(STNumberFormat.DECIMAL); + } else { + if(this.cursor != null) { + this.paragraph = this.paragraph.getDocument().insertNewParagraph(this.cursor); + this.cursor = this.paragraph.getCTP().newCursor(); + } else { + this.paragraph = this.paragraph.getDocument().createParagraph(); + } + this.run = this.paragraph.createRun(); + this.isIdentationUsed = false; + this.setDefaultIndentation(); + this.numberingLevel = this.numberingLevel.subtract(BigInteger.ONE); + ((ArrayDeque)this.abstractNumId).removeLast(); + } + break; + case "li": + if (stringBooleanEntry.getValue()) { + if(this.cursor != null) { + this.paragraph = this.paragraph.getDocument().insertNewParagraph(this.cursor); + this.cursor = this.paragraph.getCTP().newCursor(); + } else { + this.paragraph = this.paragraph.getDocument().createParagraph(); + } + // this.paragraph.setIndentationLeft(Math.round(indentation * 720) * (numberingLevel.intValue() + 1)); + this.paragraph.setIndentFromLeft(Math.round(numberingLevel.intValue() * 400 + this.indentation*400)); + this.run = this.paragraph.createRun(); + this.paragraph.setNumID(((ArrayDeque)abstractNumId).getLast()); + } + break; + case "font": + if (stringBooleanEntry.getValue()) { + if (node.hasAttr("color")) { + this.run.setColor(node.attr("color").substring(1)); + } + } else { + this.run.setColor("000000"); + } + break; + case "a": + if (stringBooleanEntry.getValue()) { + if (node.hasAttr("href")) { + this.run = createHyperLinkRun(node.attr("href")); + this.run.setColor("0000FF"); + this.run.setUnderline(UnderlinePatterns.SINGLE); + } + } else { + this.run = paragraph.createRun(); + } + break; + case "br": + if (stringBooleanEntry.getValue()) { + this.run.addBreak(); + } + break; + } + }); + } + + @Override + public void tail(Node node, int i) { + String name = node.nodeName(); + properties.put(name, false); + parseProperties(node); + properties.clear(); + } + + //GK: This function creates one numbering.xml for the word document and adds a specific format. + //It imitates the numbering.xml that is usually generated by word editors like LibreOffice + private void createNumbering(STNumberFormat.Enum format) { + CTAbstractNum ctAbstractNum = CTAbstractNum.Factory.newInstance(); + if (this.numbering == null) this.numbering = this.paragraph.getDocument().createNumbering(); + BigInteger tempNumId = BigInteger.ONE; + boolean found = false; + while (!found) { + Object o = numbering.getAbstractNum(tempNumId); + found = (o == null); + if (!found) tempNumId = tempNumId.add(BigInteger.ONE); + } + ctAbstractNum.setAbstractNumId(tempNumId); + CTLvl ctLvl = ctAbstractNum.addNewLvl(); + this.numberingLevel = numberingLevel.add(BigInteger.ONE); + ctLvl.setIlvl(numberingLevel); + ctLvl.addNewNumFmt().setVal(format); + ctLvl.addNewStart().setVal(BigInteger.ONE); + if (format == STNumberFormat.BULLET) { + ctLvl.addNewLvlJc().setVal(STJc.LEFT); + ctLvl.addNewLvlText().setVal("\u2022"); + ctLvl.addNewRPr(); //Set the Symbol font + CTFonts f = ctLvl.getRPr().addNewRFonts(); + f.setAscii("Symbol"); + f.setHAnsi("Symbol"); + f.setCs("Symbol"); + f.setHint(STHint.DEFAULT); + } else { + ctLvl.addNewLvlText().setVal("%1."); + } + XWPFAbstractNum xwpfAbstractNum = new XWPFAbstractNum(ctAbstractNum); + + this.abstractNumId.add(this.numbering.addAbstractNum(xwpfAbstractNum)); + this.numbering.addNum(((ArrayDeque)abstractNumId).getLast()); + } + + private XWPFHyperlinkRun createHyperLinkRun(String uri) { + String rId = this.paragraph.getDocument().getPackagePart().addExternalRelationship(uri, XWPFRelation.HYPERLINK.getRelation()).getId(); + + CTHyperlink cthyperLink=paragraph.getCTP().addNewHyperlink(); + cthyperLink.setId(rId); + cthyperLink.addNewR(); + + return new XWPFHyperlinkRun( + cthyperLink, + cthyperLink.getRArray(0), + paragraph + ); + } + + private void setDefaultIndentation() { + if (!isIdentationUsed) { +// this.paragraph.setIndentationLeft(Math.round(indentation * 720.0F)); + this.paragraph.setIndentFromLeft(Math.round(indentation * 400)); + this.isIdentationUsed = true; + } + } + + public XWPFParagraph getParagraph() { + return paragraph; + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/word/WordBuilder.java b/core/src/main/java/eu/eudat/file/transformer/utils/word/WordBuilder.java new file mode 100644 index 0000000..f303580 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/word/WordBuilder.java @@ -0,0 +1,1166 @@ +package eu.eudat.file.transformer.utils.word; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.eudat.file.transformer.configuration.FilePathProperties; +import eu.eudat.file.transformer.configuration.FileStorageProperties; +import eu.eudat.file.transformer.entities.descriptiontemplate.fielddata.*; +import eu.eudat.file.transformer.entities.user.components.datasetprofile.Field; +import eu.eudat.file.transformer.entities.user.components.datasetprofile.FieldSet; +import eu.eudat.file.transformer.entities.user.components.datasetprofile.Section; +import eu.eudat.file.transformer.entities.user.composite.DatasetProfilePage; +import eu.eudat.file.transformer.entities.user.composite.PagedDatasetProfile; +import eu.eudat.file.transformer.enums.FieldDataComboBoxType; +import eu.eudat.file.transformer.enums.FieldType; +import eu.eudat.file.transformer.enums.IsActive; +import eu.eudat.file.transformer.enums.ReferenceType; +import eu.eudat.file.transformer.model.*; +import eu.eudat.file.transformer.services.visibility.VisibilityRuleService; +import eu.eudat.file.transformer.utils.interfaces.ApplierWithValue; +import eu.eudat.file.transformer.utils.json.JavaToJson; +import eu.eudat.file.transformer.utils.pid.PidLoader; +import eu.eudat.file.transformer.utils.types.ParagraphStyle; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.util.Units; +import org.apache.poi.xwpf.usermodel.*; +import org.apache.xmlbeans.XmlCursor; +import org.json.JSONArray; +import org.json.JSONException; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.select.NodeTraversor; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTAbstractNum; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTLvl; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.STNumberFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.*; +import java.util.stream.Collectors; + +import static org.apache.poi.xwpf.usermodel.Document.*; + +public class WordBuilder { + private static final Logger logger = LoggerFactory.getLogger(WordBuilder.class); + private static final Map IMAGE_TYPE_MAP = Map.of( + "image/jpeg", PICTURE_TYPE_JPEG, + "image/png", PICTURE_TYPE_PNG, + "image/gif", PICTURE_TYPE_GIF, + "image/tiff", PICTURE_TYPE_TIFF, + "image/bmp", PICTURE_TYPE_BMP, + "image/wmf", PICTURE_TYPE_WMF + ); + + private Map> options = new HashMap<>(); + private Map> optionsInTable = new HashMap<>(); + private CTAbstractNum cTAbstractNum; + private BigInteger numId; + private Integer indent; + private final ObjectMapper mapper; + private Integer imageCount; + private final FilePathProperties fileTemplateProperties; + private final FileStorageProperties fileStorageProperties; + + public WordBuilder(FilePathProperties fileTemplateProperties, FileStorageProperties fileStorageProperties) { + this.fileTemplateProperties = fileTemplateProperties; + this.fileStorageProperties = fileStorageProperties; + this.cTAbstractNum = CTAbstractNum.Factory.newInstance(); + this.cTAbstractNum.setAbstractNumId(BigInteger.valueOf(1)); + this.indent = 0; + this.imageCount = 0; + this.mapper = new ObjectMapper(); + this.buildOptions(); + this.buildOptionsInTable(); + } + + private void buildOptionsInTable() { + this.optionsInTable.put(ParagraphStyle.TEXT, (mainDocumentPart, item) -> { + XWPFParagraph paragraph = mainDocumentPart.addParagraph(); + XWPFRun run = paragraph.createRun(); + if (item != null) + run.setText("" + item); + run.setFontSize(11); + return paragraph; + }); + this.optionsInTable.put(ParagraphStyle.HTML, (mainDocumentPart, item) -> { + Document htmlDoc = Jsoup.parse(((String)item).replaceAll("\n", "
")); + HtmlToWorldBuilder htmlToWorldBuilder = HtmlToWorldBuilder.convertInTable(mainDocumentPart, htmlDoc, 0); + return htmlToWorldBuilder.getParagraph(); + }); + this.optionsInTable.put(ParagraphStyle.TITLE, (mainDocumentPart, item) -> { + XWPFParagraph paragraph = mainDocumentPart.addParagraph(); + paragraph.setStyle("Title"); + paragraph.setAlignment(ParagraphAlignment.CENTER); + XWPFRun run = paragraph.createRun(); + run.setText((String)item); + run.setBold(true); + run.setFontSize(14); + return paragraph; + }); + this.optionsInTable.put(ParagraphStyle.IMAGE, (mainDocumentPart, item) -> { + XWPFParagraph paragraph = mainDocumentPart.addParagraph(); + XWPFRun run = paragraph.createRun(); + if (item != null) + run.setText(((Map)item).get("name")); + run.setFontSize(11); + run.setItalic(true); + return paragraph; + }); + } + + private void buildOptions() { + this.options.put(ParagraphStyle.TEXT, (mainDocumentPart, item) -> { + XWPFParagraph paragraph = mainDocumentPart.createParagraph(); + XWPFRun run = paragraph.createRun(); + if (item != null) + run.setText("" + item); + run.setFontSize(11); + return paragraph; + }); + this.options.put(ParagraphStyle.HTML, (mainDocumentPart, item) -> { + Document htmlDoc = Jsoup.parse(((String)item).replaceAll("\n", "
")); + HtmlToWorldBuilder htmlToWorldBuilder = HtmlToWorldBuilder.convert(mainDocumentPart, htmlDoc, this.indent); + return htmlToWorldBuilder.getParagraph(); + }); + this.options.put(ParagraphStyle.TITLE, (mainDocumentPart, item) -> { + XWPFParagraph paragraph = mainDocumentPart.createParagraph(); + paragraph.setStyle("Title"); + paragraph.setAlignment(ParagraphAlignment.CENTER); + XWPFRun run = paragraph.createRun(); + run.setText((String)item); + run.setBold(true); + run.setFontSize(14); + return paragraph; + }); + this.options.put(ParagraphStyle.HEADER1, (mainDocumentPart, item) -> { + XWPFParagraph paragraph = mainDocumentPart.createParagraph(); + paragraph.setStyle("Heading1"); + XWPFRun run = paragraph.createRun(); + run.setText((String)item); +// run.setBold(true); +// run.setFontSize(12); +// run.setStyle("0"); + return paragraph; + }); + this.options.put(ParagraphStyle.HEADER2, (mainDocumentPart, item) -> { + XWPFParagraph paragraph = mainDocumentPart.createParagraph(); + paragraph.setStyle("Heading2"); + XWPFRun run = paragraph.createRun(); + run.setText("" + item); +// run.setBold(true); +// run.setFontSize(12); + return paragraph; + }); + this.options.put(ParagraphStyle.HEADER3, (mainDocumentPart, item) -> { + XWPFParagraph paragraph = mainDocumentPart.createParagraph(); + paragraph.setStyle("Heading3"); + XWPFRun run = paragraph.createRun(); + run.setText("" + item); +// run.setBold(true); +// run.setFontSize(11); + return paragraph; + }); + this.options.put(ParagraphStyle.HEADER4, (mainDocumentPart, item) -> { + XWPFParagraph paragraph = mainDocumentPart.createParagraph(); + paragraph.setStyle("Heading4"); + XWPFRun run = paragraph.createRun(); + run.setText((String)item); + return paragraph; + }); + this.options.put(ParagraphStyle.HEADER5, (mainDocumentPart, item) -> { + XWPFParagraph paragraph = mainDocumentPart.createParagraph(); + paragraph.setStyle("Heading5"); + XWPFRun run = paragraph.createRun(); + run.setText("" + item); + return paragraph; + }); + this.options.put(ParagraphStyle.HEADER6, (mainDocumentPart, item) -> { + XWPFParagraph paragraph = mainDocumentPart.createParagraph(); + paragraph.setStyle("Heading6"); + XWPFRun run = paragraph.createRun(); + run.setText("" + item); + return paragraph; + }); + this.options.put(ParagraphStyle.FOOTER, (mainDocumentPart, item) -> { + XWPFParagraph paragraph = mainDocumentPart.createParagraph(); + XWPFRun run = paragraph.createRun(); + run.setText((String)item); + return paragraph; + }); + this.options.put(ParagraphStyle.COMMENT, (mainDocumentPart, item) -> { + XWPFParagraph paragraph = mainDocumentPart.createParagraph(); + XWPFRun run = paragraph.createRun(); + run.setText("" + item); + run.setItalic(true); + return paragraph; + }); + this.options.put(ParagraphStyle.IMAGE, (mainDocumentPart, item) -> { + XWPFParagraph paragraph = mainDocumentPart.createParagraph(); + paragraph.setPageBreak(true); + paragraph.setSpacingAfter(0); + paragraph.setAlignment(ParagraphAlignment.CENTER); //GK: Center the image if it is too small + XWPFRun run = paragraph.createRun(); + String imageId = ((Map)item).get("id"); + String fileName = ((Map)item).get("name"); + String fileType = ((Map)item).get("type"); + int format; + format = IMAGE_TYPE_MAP.getOrDefault(fileType, 0); + try { + FileInputStream image = new FileInputStream(fileStorageProperties.getTemp() + imageId); + ImageInputStream iis = ImageIO.createImageInputStream(new File(fileStorageProperties.getTemp() + imageId)); + Iterator readers = ImageIO.getImageReaders(iis); + if (readers.hasNext()) { + ImageReader reader = readers.next(); + reader.setInput(iis); + + int initialImageWidth = reader.getWidth(0); + int initialImageHeight = reader.getHeight(0); + + float ratio = initialImageHeight / (float)initialImageWidth; + + int marginLeftInDXA = (int) mainDocumentPart.getDocument().getBody().getSectPr().getPgMar().getLeft(); + int marginRightInDXA = (int) mainDocumentPart.getDocument().getBody().getSectPr().getPgMar().getRight(); + int pageWidthInDXA = (int) mainDocumentPart.getDocument().getBody().getSectPr().getPgSz().getW(); + int pageWidth = Math.round((pageWidthInDXA - marginLeftInDXA - marginRightInDXA) / (float)20); // /20 converts dxa to points + + int imageWidth = Math.round(initialImageWidth*(float)0.75); // *0.75 converts pixels to points + int width = Math.min(imageWidth, pageWidth); + + int marginTopInDXA = (int) mainDocumentPart.getDocument().getBody().getSectPr().getPgMar().getTop(); + int marginBottomInDXA = (int) mainDocumentPart.getDocument().getBody().getSectPr().getPgMar().getBottom(); + int pageHeightInDXA = (int) mainDocumentPart.getDocument().getBody().getSectPr().getPgSz().getH(); + int pageHeight = Math.round((pageHeightInDXA - marginTopInDXA - marginBottomInDXA) / (float)20); // /20 converts dxa to points + + int imageHeight = Math.round(initialImageHeight * ((float)0.75)); // *0.75 converts pixels to points + + int height = Math.round(width*ratio); + if(height > pageHeight) { + // height calculated with ratio is too large. Image may have Portrait (vertical) orientation. Recalculate image dimensions. + height = Math.min(imageHeight, pageHeight); + width = Math.round(height/ratio); + } + + XWPFPicture picture = run.addPicture(image, format, fileName, Units.toEMU(width), Units.toEMU(height)); + paragraph.setPageBreak(false); + imageCount++; + XWPFParagraph captionParagraph = mainDocumentPart.createParagraph(); + captionParagraph.setAlignment(ParagraphAlignment.CENTER); + captionParagraph.setSpacingBefore(0); + captionParagraph.setStyle("Caption"); + XWPFRun captionRun = captionParagraph.createRun(); + captionRun.setText("Image " + imageCount); + + } + } catch (IOException | InvalidFormatException e){ + logger.error(e.getMessage(), e); + } + return paragraph; + }); + } + + public XWPFDocument build(XWPFDocument document, PagedDatasetProfile pagedDatasetProfile, VisibilityRuleService visibilityRuleService) throws IOException { +// createPages(pagedDatasetProfile.getPages(), document, true, visibilityRuleService); +// XWPFNumbering numbering = document.createNumbering(); +// BigInteger tempNumId = BigInteger.ONE; +// boolean found = false; +// while (!found) { +// Object o = numbering.getAbstractNum(tempNumId); +// found = (o == null); +// if (!found) tempNumId = tempNumId.add(BigInteger.ONE); +// } +// cTAbstractNum.setAbstractNumId(tempNumId); +// XWPFAbstractNum abstractNum = new XWPFAbstractNum(cTAbstractNum); +// BigInteger abstractNumID = numbering.addAbstractNum(abstractNum); +// this.numId = numbering.addNum(abstractNumID); + + createPages(pagedDatasetProfile.getPages(), document, false, visibilityRuleService); + return document; + } + + private void createPages(List datasetProfilePages, XWPFDocument mainDocumentPart, Boolean createListing, VisibilityRuleService visibilityRuleService) { + datasetProfilePages.forEach(item -> { + try { + createSections(item.getSections(), mainDocumentPart, ParagraphStyle.HEADER5, 0, createListing, visibilityRuleService, item.getOrdinal() + 1, null); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + }); + } + + private void createSections(List
sections, XWPFDocument mainDocumentPart, ParagraphStyle style, Integer indent, Boolean createListing, VisibilityRuleService visibilityRuleService, Integer page, String sectionString) { + if (createListing) this.addListing(mainDocumentPart, indent, false, true); + boolean hasValue = false; + for (Section section: sections) { + int paragraphPos = -1; + String tempSectionString = sectionString != null ? sectionString + "." + (section.getOrdinal() + 1) : "" + (section.getOrdinal() + 1); + if (visibilityRuleService.isElementVisible(section.getId())) { + if (!createListing) { + XWPFParagraph paragraph = addParagraphContent(page + "." + tempSectionString + " " + section.getTitle(), mainDocumentPart, style, numId, indent); +// CTDecimalNumber number = paragraph.getCTP().getPPr().getNumPr().addNewIlvl(); +// number.setVal(BigInteger.valueOf(indent)); + paragraphPos = mainDocumentPart.getPosOfParagraph(paragraph); + } + createSections(section.getSections(), mainDocumentPart, ParagraphStyle.HEADER5, indent+1, createListing, visibilityRuleService, page, tempSectionString); + hasValue = createCompositeFields(section.getCompositeFields(), mainDocumentPart, indent+1, createListing, visibilityRuleService, page, tempSectionString); + + if (!hasValue && paragraphPos > -1) { + mainDocumentPart.removeBodyElement(paragraphPos); + } + } + } + } + + private Boolean createCompositeFields(List
compositeFields, XWPFDocument mainDocumentPart, Integer indent, Boolean createListing, VisibilityRuleService visibilityRuleService, Integer page, String section) { + if (createListing) this.addListing(mainDocumentPart, indent, true, true); + boolean hasValue = false; + boolean returnedValue = false; + + for (FieldSet compositeField: compositeFields) { + if (visibilityRuleService.isElementVisible(compositeField.getId()) && hasVisibleFields(compositeField, visibilityRuleService)) { + char c = 'a'; + int multiplicityItems = 0; + boolean hasMultiplicityItems = false; + int paragraphPos = -1; + int paragraphPosInner = -1; + if (compositeField.getTitle() != null && !compositeField.getTitle().isEmpty() && !createListing) { + XWPFParagraph paragraph = addParagraphContent(page + "." + section + "." + (compositeField.getOrdinal() +1) + " " + compositeField.getTitle(), mainDocumentPart, ParagraphStyle.HEADER6, numId, indent); +// CTDecimalNumber number = paragraph.getCTP().getPPr().getNumPr().addNewIlvl(); +// number.setVal(BigInteger.valueOf(indent)); + paragraphPos = mainDocumentPart.getPosOfParagraph(paragraph); + if(compositeField.getMultiplicity() != null && !compositeField.getMultiplicity().getTableView() && compositeField.getMultiplicityItems() != null && !compositeField.getMultiplicityItems().isEmpty()){ + XWPFParagraph paragraphInner = addParagraphContent(c + ". ", mainDocumentPart, ParagraphStyle.TEXT, numId, indent); + paragraphPosInner = mainDocumentPart.getPosOfParagraph(paragraphInner); + hasMultiplicityItems = true; + multiplicityItems++; + } + } + XWPFTable tbl = null; + XWPFTableRow row = null; + int numOfRows = 0; + if(compositeField.getMultiplicity() != null && compositeField.getMultiplicity().getTableView()) { + tbl = mainDocumentPart.createTable(); + tbl.setTableAlignment(TableRowAlign.CENTER); + mainDocumentPart.createParagraph(); + createHeadersInTable(compositeField.getFields(), tbl, visibilityRuleService); + numOfRows = tbl.getRows().size(); + row = tbl.createRow(); + } + if(compositeField.getMultiplicity() != null && compositeField.getMultiplicity().getTableView()) { + hasValue = createFieldsInTable(compositeField.getFields(), row, indent, createListing, visibilityRuleService, hasMultiplicityItems, numOfRows); + numOfRows++; + } else { + hasValue = createFields(compositeField.getFields(), mainDocumentPart, indent, createListing, visibilityRuleService, hasMultiplicityItems); + } + if(hasValue){ + returnedValue = true; + } else if(paragraphPosInner > -1){ + mainDocumentPart.removeBodyElement(paragraphPosInner); + c--; + multiplicityItems--; + } + //TODO: Multiplicity Items??? + if (compositeField.getMultiplicityItems() != null && !compositeField.getMultiplicityItems().isEmpty()) { + List
list = compositeField.getMultiplicityItems().stream().sorted(Comparator.comparingInt(FieldSet::getOrdinal)).collect(Collectors.toList()); + for (FieldSet multiplicityFieldset : list) { + paragraphPosInner = -1; + if(!compositeField.getMultiplicity().getTableView() && !createListing){ + c++; +// addParagraphContent(c + ". ", mainDocumentPart, ParagraphStyle.HEADER6, numId); + XWPFParagraph paragraphInner = addParagraphContent(c + ". ", mainDocumentPart, ParagraphStyle.TEXT, numId, indent); + paragraphPosInner = mainDocumentPart.getPosOfParagraph(paragraphInner); + hasMultiplicityItems = true; + multiplicityItems++; + } +// hasValue = createFields(multiplicityFieldset.getFields(), mainDocumentPart, 3, createListing, visibilityRuleService, hasMultiplicityItems); + boolean hasValueInner = false; + if(compositeField.getMultiplicity().getTableView()) { + row = tbl.createRow(); + hasValueInner = createFieldsInTable(multiplicityFieldset.getFields(), row, indent, createListing, visibilityRuleService, hasMultiplicityItems, numOfRows); + numOfRows++; + } else { + hasValueInner = createFields(multiplicityFieldset.getFields(), mainDocumentPart, indent, createListing, visibilityRuleService, hasMultiplicityItems); + } +// if(hasValue){ + if(hasValueInner){ + hasValue = true; + returnedValue = true; + } else if(paragraphPosInner > -1){ + mainDocumentPart.removeBodyElement(paragraphPosInner); + c--; + multiplicityItems--; + } + } + if(multiplicityItems == 1) { + String text = mainDocumentPart.getLastParagraph().getRuns().get(0).getText(0); + if(text.equals("a. ")) { + mainDocumentPart.getLastParagraph().removeRun(0); + } + } + } + //TODO: Dataset Profile +// if (hasValue && compositeField.getHasCommentField() && compositeField.getCommentFieldValue() != null && !compositeField.getCommentFieldValue().isEmpty() && !createListing) { +// XWPFParagraph paragraph = addParagraphContent("Comment:\n"+compositeField.getCommentFieldValue(), mainDocumentPart, ParagraphStyle.HTML, numId, indent); +//// CTDecimalNumber number = paragraph.getCTP().getPPr().getNumPr().addNewIlvl(); +//// number.setVal(BigInteger.valueOf(indent)); +// } + if (!hasValue && paragraphPos > -1) { + mainDocumentPart.removeBodyElement(paragraphPos); + } + } + } + + return returnedValue; + } + + private void createHeadersInTable(List fields, XWPFTable table, VisibilityRuleService visibilityRuleService) { + boolean atLeastOneHeader = false; + List tempFields = fields.stream().sorted(Comparator.comparingInt(Field::getOrdinal)).collect(Collectors.toList()); + int index = 0; + XWPFTableRow row = table.getRow(0); + for (Field field: tempFields) { + if (visibilityRuleService.isElementVisible(field.getId()) && field.getExport()) { + XWPFTableCell cell; + if(index == 0) { + cell = row.getCell(0); + } else { + cell = row.createCell(); + } + cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.valueOf("CENTER")); + String label = ((BaseFieldDataEntity) field.getData()).getLabel(); + if(label != null && label != "") { + XWPFParagraph paragraph = cell.getParagraphs().get(0); + paragraph.setIndentationFirstLine(50); + XWPFRun run = paragraph.createRun(); + run.setText(label); + run.setBold(true); + run.setFontSize(12); + paragraph.setAlignment(ParagraphAlignment.CENTER); + paragraph.setSpacingBefore(100); + + atLeastOneHeader = true; + } + } + index++; + } + + if(!atLeastOneHeader) { + table.removeRow(0); + } + } + + private Boolean createFieldsInTable(List fields, XWPFTableRow mainDocumentPart, Integer indent, Boolean createListing, VisibilityRuleService visibilityRuleService, boolean hasMultiplicityItems, int numOfRows) { + int numOfCells = 0; + boolean hasValue = false; + List tempFields = fields.stream().sorted(Comparator.comparingInt(Field::getOrdinal)).collect(Collectors.toList()); + for (Field field: tempFields) { + if (visibilityRuleService.isElementVisible(field.getId()) && field.getExport()) { + if (!createListing) { + try { + if(((BaseFieldDataEntity) field.getData()).getFieldType().equals(FieldType.UPLOAD)){ + boolean isImage = false; + for(UploadDataEntity.Option type: ((UploadDataEntity)field.getData()).getTypes()){ + String fileFormat = type.getValue(); + if(IMAGE_TYPE_MAP.containsKey(fileFormat)){ + isImage = true; + break; + } + } + if(isImage){ + if (field.getValue() != null && !field.getValue().toString().isEmpty()) { + XWPFParagraph paragraph = addCellContent(mapper.convertValue(field.getValue(), Map.class), mainDocumentPart, ParagraphStyle.IMAGE, numId, 0, numOfRows, numOfCells, 0); + if (paragraph != null) { + hasValue = true; + } + if(hasMultiplicityItems){ + hasMultiplicityItems = false; + } + } + } + } + else if (field.getValue() != null && !field.getValue().toString().isEmpty()) { + this.indent = indent; + String format = this.formatter(field); + if (((BaseFieldDataEntity) field.getData()).getFieldType().equals(FieldType.TAGS)) { + format = getCommaSeparatedFormatsFromJson(format, "name"); + } else if (((BaseFieldDataEntity) field.getData()).getFieldType().equals(FieldType.COMBO_BOX) && field.getData() instanceof AutoCompleteDataEntity) { + format = getCommaSeparatedFormatsFromJson(format, "label"); + } + boolean isResearcher = ((BaseFieldDataEntity) field.getData()).getFieldType().equals(FieldType.RESEARCHERS); + if(format != null && !format.isEmpty()){ + Object hasMultiAutoComplete = mapper.convertValue(field.getData(), Map.class).get("multiAutoComplete"); + boolean isMultiAutoComplete = hasMultiAutoComplete != null && (boolean)hasMultiAutoComplete; + boolean arrayStringFormat = format.charAt(0) == '['; + if(arrayStringFormat || isMultiAutoComplete){ + List values = (arrayStringFormat) ? Arrays.asList(format.substring(1, format.length() - 1).split(",[ ]*")) : Arrays.asList(format.split(",[ ]*")); + if(values.size() > 1) { + boolean orcidResearcher; + int numOfValuesInCell = 0; + for (String val : values) { + orcidResearcher = false; + String orcId = null; + if(isResearcher && val.contains("orcid:")){ + orcId = val.substring(val.indexOf(':') + 1, val.indexOf(')')); + val = val.substring(0, val.indexOf(':') + 1) + " "; + orcidResearcher = true; + } + format = "• " + val; + if(hasMultiplicityItems){ + XWPFParagraph paragraph = mainDocumentPart.getCell(mainDocumentPart.getTableCells().size()).addParagraph(); + paragraph.createRun().setText(format); + if(orcidResearcher){ + XWPFHyperlinkRun run = paragraph.createHyperlinkRun("https://orcid.org/" + orcId); + run.setText(orcId); + run.setUnderline(UnderlinePatterns.SINGLE); + run.setColor("0000FF"); + paragraph.createRun().setText(")"); + } + hasMultiplicityItems = false; + } + else{ + XWPFParagraph paragraph = addCellContent(format, mainDocumentPart, ((BaseFieldDataEntity) field.getData()).getFieldType().equals(FieldType.RICH_TEXT_AREA) ? ParagraphStyle.HTML : ParagraphStyle.TEXT, numId, indent, numOfRows, numOfCells, numOfValuesInCell); + numOfValuesInCell++; + if(orcidResearcher){ + XWPFHyperlinkRun run = paragraph.createHyperlinkRun("https://orcid.org/" + orcId); + run.setText(orcId); + run.setUnderline(UnderlinePatterns.SINGLE); + run.setColor("0000FF"); + paragraph.createRun().setText(")"); + } + if (paragraph != null) { + hasValue = true; + } + } + format = null; + } + } + else if(values.size() == 1){ + format = values.get(0); + } + } + } + if(hasMultiplicityItems && format != null){ + XWPFParagraph paragraph = mainDocumentPart.getCell(mainDocumentPart.getTableCells().size()).addParagraph(); + paragraph.createRun().setText(format); + hasMultiplicityItems = false; + hasValue = true; + } + else{ + XWPFParagraph paragraph = addCellContent(format, mainDocumentPart, ((BaseFieldDataEntity) field.getData()).getFieldType().equals(FieldType.RICH_TEXT_AREA) ? ParagraphStyle.HTML : ParagraphStyle.TEXT, numId, indent, numOfRows, numOfCells, 0); + if (paragraph != null) { + hasValue = true; + } + } + } + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + } + numOfCells++; + } + } + return hasValue; + } + + private void createHypeLink(XWPFDocument mainDocumentPart, String format, String pidType, String pid, boolean hasMultiplicityItems, boolean isMultiAutoComplete){ + PidLink pidLink = PidLoader.loadPidLinks(this.fileTemplateProperties.getPidTemplate()).stream().filter(pl -> pl.getPid().equals(pidType)).findFirst().orElse(null); + if (pidLink != null) { + if (!hasMultiplicityItems) { + XWPFParagraph paragraph = mainDocumentPart.createParagraph(); + paragraph.setIndentFromLeft(400 * indent); + if (numId != null) { + paragraph.setNumID(numId); + } + } + if (isMultiAutoComplete) { + XWPFRun r = mainDocumentPart.getLastParagraph().createRun(); + r.setText("• "); + } + XWPFHyperlinkRun run = mainDocumentPart.getLastParagraph().createHyperlinkRun(pidLink.getLink().replace("{pid}", pid)); + run.setText(format); + run.setUnderline(UnderlinePatterns.SINGLE); + run.setColor("0000FF"); + run.setFontSize(11); + } + else { + String newFormat = (isMultiAutoComplete) ? "• " + format : format; + if (hasMultiplicityItems) { + mainDocumentPart.getLastParagraph().createRun().setText(newFormat); + } + else { + addParagraphContent(newFormat, mainDocumentPart, ParagraphStyle.TEXT, numId, indent); + } + } + } + + private Boolean createFields(List fields, XWPFDocument mainDocumentPart, Integer indent, Boolean createListing, VisibilityRuleService visibilityRuleService, boolean hasMultiplicityItems) { + if (createListing) this.addListing(mainDocumentPart, indent, false, false); + boolean hasValue = false; + List tempFields = fields.stream().sorted(Comparator.comparingInt(Field::getOrdinal)).collect(Collectors.toList()); + for (Field field: tempFields) { + if (visibilityRuleService.isElementVisible(field.getId()) && field.getExport()) { + if (!createListing) { + try { + if(((BaseFieldDataEntity) field.getData()).getFieldType().equals(FieldType.UPLOAD)){ + boolean isImage = false; + for(UploadDataEntity.Option type: ((UploadDataEntity)field.getData()).getTypes()){ + String fileFormat = type.getValue(); + if(IMAGE_TYPE_MAP.containsKey(fileFormat)){ + isImage = true; + break; + } + } + if(isImage){ + if (field.getValue() != null && !field.getValue().toString().isEmpty()) { + XWPFParagraph paragraph = addParagraphContent(mapper.convertValue(field.getValue(), Map.class), mainDocumentPart, ParagraphStyle.IMAGE, numId, 0); + if (paragraph != null) { +// CTDecimalNumber number = paragraph.getCTP().getPPr().getNumPr().addNewIlvl(); +// number.setVal(BigInteger.valueOf(indent)); + hasValue = true; + } + if(hasMultiplicityItems){ + hasMultiplicityItems = false; + } + } + } + } + else if (field.getValue() != null && !field.getValue().toString().isEmpty()) { + this.indent = indent; + String format = this.formatter(field); + if (((BaseFieldDataEntity) field.getData()).getFieldType().equals(FieldType.TAGS)) { + format = getCommaSeparatedFormatsFromJson(format, "name"); + } else if (((BaseFieldDataEntity) field.getData()).getFieldType().equals(FieldType.COMBO_BOX) && field.getData() instanceof AutoCompleteDataEntity) { + format = getCommaSeparatedFormatsFromJson(format, "label"); + } + switch (((BaseFieldDataEntity) field.getData()).getFieldType()) { + case ORGANIZATIONS: + case EXTERNAL_DATASETS: + case PUBLICATIONS: + if(format != null && !format.isEmpty()){ + Object hasMultiAutoComplete = mapper.convertValue(field.getData(), Map.class).get("multiAutoComplete"); + boolean isMultiAutoComplete = hasMultiAutoComplete != null && (boolean)hasMultiAutoComplete; + if(!isMultiAutoComplete){ + Map value = mapper.readValue((String)field.getValue(), Map.class); + if(hasMultiplicityItems){ + createHypeLink(mainDocumentPart, format, value.get("pidTypeField"), value.get("pid"), true, false); + hasMultiplicityItems = false; + } + else{ + createHypeLink(mainDocumentPart, format, value.get("pidTypeField"), value.get("pid"), false, false); + } + } + else{ + mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + List> values = new ArrayList<>(); + try { + values = Arrays.asList(mapper.readValue(field.getValue().toString(), HashMap[].class)); + } + catch (Exception e) { + Map map = new HashMap<>(); + map.put("label", field.getValue()); + values.add(map); + } + if (values.size() > 1) { + for (Map value : values) { + if(hasMultiplicityItems){ + createHypeLink(mainDocumentPart, (String) value.get("name"), (String) value.get("pidTypeField"), (String) value.get("pid"), true, true); + hasMultiplicityItems = false; + } + else{ + createHypeLink(mainDocumentPart, (String) value.get("name"), (String) value.get("pidTypeField"), (String) value.get("pid"), false, true); + } + } + } + else if(values.size() == 1){ + if(hasMultiplicityItems){ + createHypeLink(mainDocumentPart, format, (String) values.get(0).get("pidTypeField"), (String) values.get(0).get("pid"), true, false); + hasMultiplicityItems = false; + } + else{ + createHypeLink(mainDocumentPart, format, (String) values.get(0).get("pidTypeField"), (String) values.get(0).get("pid"), false, false); + } + } + } + hasValue = true; + } + break; + default: + boolean isResearcher = ((BaseFieldDataEntity) field.getData()).getFieldType().equals(FieldType.RESEARCHERS); + if(format != null && !format.isEmpty()){ + Object hasMultiAutoComplete = mapper.convertValue(field.getData(), Map.class).get("multiAutoComplete"); + boolean isMultiAutoComplete = hasMultiAutoComplete != null && (boolean)hasMultiAutoComplete; + boolean arrayStringFormat = format.charAt(0) == '['; + if(arrayStringFormat || isMultiAutoComplete){ + List values = (arrayStringFormat) ? Arrays.asList(format.substring(1, format.length() - 1).split(",[ ]*")) : Arrays.asList(format.split(",[ ]*")); + if(values.size() > 1) { + boolean orcidResearcher; + for (String val : values) { + orcidResearcher = false; + String orcId = null; + if(isResearcher && val.contains("orcid:")){ + orcId = val.substring(val.indexOf(':') + 1, val.indexOf(')')); + val = val.substring(0, val.indexOf(':') + 1) + " "; + orcidResearcher = true; + } + format = "• " + val; + if(hasMultiplicityItems){ + mainDocumentPart.getLastParagraph().createRun().setText(format); + if(orcidResearcher){ + XWPFHyperlinkRun run = mainDocumentPart.getLastParagraph().createHyperlinkRun("https://orcid.org/" + orcId); + run.setText(orcId); + run.setUnderline(UnderlinePatterns.SINGLE); + run.setColor("0000FF"); + mainDocumentPart.getLastParagraph().createRun().setText(")"); + } + hasMultiplicityItems = false; + } + else{ + XWPFParagraph paragraph = addParagraphContent(format, mainDocumentPart, ((BaseFieldDataEntity) field.getData()).getFieldType().equals(FieldType.RICH_TEXT_AREA) ? ParagraphStyle.HTML : ParagraphStyle.TEXT, numId, indent); + if(orcidResearcher){ + XWPFHyperlinkRun run = paragraph.createHyperlinkRun("https://orcid.org/" + orcId); + run.setText(orcId); + run.setUnderline(UnderlinePatterns.SINGLE); + run.setColor("0000FF"); + paragraph.createRun().setText(")"); + } + if (paragraph != null) { +// CTDecimalNumber number = paragraph.getCTP().getPPr().getNumPr().addNewIlvl(); +// number.setVal(BigInteger.valueOf(indent)); + hasValue = true; + } + } + format = null; + } + } + else if(values.size() == 1){ + format = values.get(0); + } + } + } + if (format != null) { + if (hasMultiplicityItems) { + mainDocumentPart.getLastParagraph().createRun().setText(format); + hasMultiplicityItems = false; + hasValue = true; + } + else { + XWPFParagraph paragraph = addParagraphContent(format, mainDocumentPart, ((BaseFieldDataEntity) field.getData()).getFieldType().equals(FieldType.RICH_TEXT_AREA) ? ParagraphStyle.HTML : ParagraphStyle.TEXT, numId, indent); + if (paragraph != null) { +// CTDecimalNumber number = paragraph.getCTP().getPPr().getNumPr().addNewIlvl(); +// number.setVal(BigInteger.valueOf(indent)); + hasValue = true; + } + } + } + } + } + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + } + } + } + return hasValue; + } + + private String getCommaSeparatedFormatsFromJson(String format, String attribute){ + if((format == null || format.isEmpty()) || (attribute == null || attribute.isEmpty())){ + return null; + } + try { + JSONArray array = new JSONArray(JavaToJson.objectStringToJson(format)); + StringBuilder multipleFormats = new StringBuilder(); + for (int i = 0; i < array.length(); i++) { + multipleFormats.append(array.getJSONObject(i).getString(attribute)).append(", "); + } + if (multipleFormats.length() > 0) { + multipleFormats.setLength(multipleFormats.length() - 2); + } + return multipleFormats.toString(); + } catch (JSONException e) { + return format; + } + } + + public XWPFParagraph addCellContent(Object content, XWPFTableRow mainDocumentPart, ParagraphStyle style, BigInteger numId, int indent, int numOfRows, int numOfCells, int numOfValuesInCell) { + if (content != null) { + if (content instanceof String && ((String)content).isEmpty()) { + return null; + } + this.indent = indent; + XWPFTableCell cell; + if(numOfRows > 0 || numOfValuesInCell > 0) { + cell = mainDocumentPart.getCell(numOfCells); + } else { + cell = mainDocumentPart.createCell(); + } + cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.valueOf("CENTER")); + if(numOfValuesInCell == 0) { + cell.removeParagraph(0); + } + + XWPFParagraph paragraph = this.optionsInTable.get(style).apply(cell, content); + if (paragraph != null) { + paragraph.setAlignment(ParagraphAlignment.CENTER); + paragraph.setSpacingBefore(100); + if (numId != null) { + paragraph.setNumID(numId); + } + return paragraph; + } + } + return null; + } + + public XWPFParagraph addParagraphContent(Object content, XWPFDocument mainDocumentPart, ParagraphStyle style, BigInteger numId, int indent) { +// this.indent = 0; + if (content != null) { + if (content instanceof String && ((String)content).isEmpty()) { + return null; + } + this.indent = indent; + XWPFParagraph paragraph = this.options.get(style).apply(mainDocumentPart, content); + if (paragraph != null) { + paragraph.setIndentFromLeft(400*indent); + if (numId != null) { + paragraph.setNumID(numId); + } + return paragraph; + } + } + return null; + } + + private void addListing(XWPFDocument document, int indent, Boolean question, Boolean hasIndication) { + CTLvl cTLvl = this.cTAbstractNum.addNewLvl(); + + String textLevel = ""; + for (int i = 0; i <= indent; i++) { + textLevel += "%" + (i + 1) + "."; + } + + if (question) { + cTLvl.addNewNumFmt().setVal(STNumberFormat.DECIMAL); +// cTLvl.addNewLvlText().setVal(""); + cTLvl.setIlvl(BigInteger.valueOf(indent)); + } else if (!question && hasIndication) { + cTLvl.addNewNumFmt().setVal(STNumberFormat.DECIMAL); +// cTLvl.addNewLvlText().setVal(""); + cTLvl.setIlvl(BigInteger.valueOf(indent)); + } + if (!question && !hasIndication) { + cTLvl.addNewNumFmt().setVal(STNumberFormat.NONE); +// cTLvl.addNewLvlText().setVal(""); + cTLvl.setIlvl(BigInteger.valueOf(indent)); + } + } + + private String formatter(Field field) throws IOException { + FieldDataComboBoxType comboboxType = null; + if (field.getValue() == null) { + return null; + } + switch (((BaseFieldDataEntity) field.getData()).getFieldType()) { + case RESEARCHERS: +// case "projects": //TODO: Description Templatedefinition + case ORGANIZATIONS: + case EXTERNAL_DATASETS: + case DATA_REPOSITORIES: + case PUB_REPOSITORIES: + case JOURNAL_REPOSITORIES: + case TAXONOMIES: + case LICENSES: + case PUBLICATIONS: + case REGISTRIES: + case SERVICES: + case TAGS: + case CURRENCY: + comboboxType = FieldDataComboBoxType.Autocomplete; + case COMBO_BOX: { + if (comboboxType == null) { + comboboxType = ((ComboBoxDataEntity) field.getData()).getFieldSubType(); + } + if (comboboxType.equals(FieldDataComboBoxType.Autocomplete)) { + mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + if (field.getValue() == null) return null; + List> mapList = new ArrayList<>(); + if (!field.getValue().equals("") && field.getValue().toString() != null) { + try { + mapList = Arrays.asList(mapper.readValue(field.getValue().toString(), HashMap[].class)); + }catch (Exception e) { + // logger.warn(e.getMessage(), e); + // logger.info("Moving to fallback parsing"); + Map map = new HashMap<>(); + map.put("label", field.getValue().toString()); + mapList.add(map); + } + } + StringBuilder sb = new StringBuilder(); + int index = 0; + for (Map map: mapList) { + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() != null && (entry.getKey().equals("label") || entry.getKey().equals("description") || entry.getKey().equals("name"))) { + sb.append(entry.getValue()); + break; + } + } + if (index != mapList.size() - 1) sb.append(", "); + index++; + } + return sb.toString(); + } else if (comboboxType.equals(FieldDataComboBoxType.Wordlist)) { + WordListDataEntity wordListDataEntity = (WordListDataEntity) field.getData(); + if (field.getValue() != null){ + ComboBoxDataEntity.Option selectedOption = null; + if (!wordListDataEntity.getOptions().isEmpty()) { + for (ComboBoxDataEntity.Option option : wordListDataEntity.getOptions()) { + if (option.getValue().equals(field.getValue())) { + selectedOption = option; + } + } + } + return selectedOption != null ? selectedOption.getLabel() : field.getValue().toString(); + } + return ""; + } + } + case BOOLEAN_DECISION: + if (field.getValue() != null && field.getValue().equals("true")) return "Yes"; + if (field.getValue() != null && field.getValue().equals("false")) return "No"; + return null; + case RADIO_BOX: + return field.getValue() != null ? field.getValue().toString() : null; + case CHECK_BOX: + CheckBoxDataEntity data = (CheckBoxDataEntity) field.getData(); + if (field.getValue() == null || field.getValue().equals("false")) return null; + return data.getLabel(); + case DATE_PICKER:{ + Instant instant; + if (!((String)field.getValue()).isEmpty()) { + try { + instant = Instant.parse((String) field.getValue()); + } catch (DateTimeParseException ex) { + instant = LocalDate.parse((String) field.getValue()).atStartOfDay().toInstant(ZoneOffset.UTC); + } + return field.getValue() != null ? DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.systemDefault()).format(instant) : ""; + } + return (String) field.getValue(); + } + case FREE_TEXT: + case TEXT_AREA: + case RICH_TEXT_AREA: + return field.getValue() != null ? field.getValue().toString(): ""; + case DATASET_IDENTIFIER: + case VALIDATION: + if (field.getValue() != null && !field.getValue().toString().isEmpty()) { + Map identifierData; + try { + identifierData = mapper.readValue(field.getValue().toString(), HashMap.class); + } catch (Exception ex) { + // logger.warn(ex.getLocalizedMessage(), ex); + // logger.info("Reverting to custom parsing"); + identifierData = customParse(field.getValue().toString()); + } + return "id: " + identifierData.get("identifier") + ", Type: " + identifierData.get("type"); + } + return ""; + } + return null; + } + + private boolean hasVisibleFields(FieldSet compositeFields, VisibilityRuleService visibilityRuleService) { + return compositeFields.getFields().stream().anyMatch(field -> visibilityRuleService.isElementVisible(field.getId()) && field.getExport()); + } + + private Map customParse(String value) { + Map result = new LinkedHashMap<>(); + String parsedValue = value.replaceAll("[^a-zA-Z0-9\\s:=,]", ""); + StringTokenizer commaTokens = new StringTokenizer(parsedValue, ", "); + String delimeter = parsedValue.contains("=") ? "=" : ":"; + while (commaTokens.hasMoreTokens()) { + String token = commaTokens.nextToken(); + StringTokenizer delimiterTokens = new StringTokenizer(token, delimeter); + result.put(delimiterTokens.nextToken(), delimiterTokens.nextToken()); + } + return result; + } + + public int findPosOfPoweredBy(XWPFDocument document) { + for (XWPFParagraph p : document.getParagraphs()) { + List runs = p.getRuns(); + if (runs != null) { + for (XWPFRun r : runs) { + String text = r.getText(0); + if (text != null) { + if (text.equals("Powered by")) { + return document.getPosOfParagraph(p) - 1; + } + } + } + } + } + return -1; + } + + public void fillFirstPage(DmpFileTransformerModel dmpEntity, DescriptionFileTransformerModel descriptionFileTransformerModelEntityEntity, XWPFDocument document, boolean isDataset) { + int parPos = 0; + int descrParPos = -1; + XWPFParagraph descrPar = null; + List grants = dmpEntity.getDmpReferences().stream().map(DmpReference::getReference).filter(referenceFileModel -> referenceFileModel.getType().equals(ReferenceType.Grants) && referenceFileModel.getIsActive().equals(IsActive.Active)).toList(); + List researchers = dmpEntity.getDmpReferences().stream().map(DmpReference::getReference).filter(reference -> reference.getType().equals(ReferenceType.Researcher)).toList(); + List organizations = dmpEntity.getDmpReferences().stream().map(DmpReference::getReference).filter(referenceFileModel -> referenceFileModel.getType().equals(ReferenceType.Organizations)).toList(); + List funders = dmpEntity.getDmpReferences().stream().map(DmpReference::getReference).filter(referenceFileModel -> referenceFileModel.getType().equals(ReferenceType.Funder) && referenceFileModel.getIsActive().equals(IsActive.Active)).toList(); + + for(XWPFParagraph p: document.getParagraphs()){ + List runs = p.getRuns(); + if(runs != null){ + for(XWPFRun r : runs){ + String text = r.getText(0); + if(text != null){ + if(text.contains("{ARGOS.DMP.TITLE}")) { + text = text.replace("{ARGOS.DMP.TITLE}", dmpEntity.getLabel()); + r.setText(text, 0); + } else if(text.contains("{ARGOS.DMP.VERSION}")) { + text = text.replace("{ARGOS.DMP.VERSION}", "Version " + dmpEntity.getVersion()); + r.setText(text, 0); + } else if(descriptionFileTransformerModelEntityEntity != null && text.contains("{ARGOS.DATASET.TITLE}")) { + text = text.replace("{ARGOS.DATASET.TITLE}", descriptionFileTransformerModelEntityEntity.getLabel()); + r.setText(text, 0); +// } else if(text.equals("Description") && ((!isDataset && (dmpEntity == null || dmpEntity.getDescription() != null)) || (isDataset && (datasetEntity == null || datasetEntity.getDescription() == null)))) { +// r.setText("", 0); + } else if((dmpEntity != null && text.contains("{ARGOS.DMP.DESCRIPTION}") && !isDataset) || (descriptionFileTransformerModelEntityEntity != null && text.contains("{ARGOS.DATASET.DESCRIPTION}") && isDataset)) { + descrParPos = parPos; + descrPar = p; + if(dmpEntity != null && !isDataset) { + text = text.replace("{ARGOS.DMP.DESCRIPTION}", ""); + } else if(descriptionFileTransformerModelEntityEntity != null && isDataset) { + text = text.replace("{ARGOS.DATASET.DESCRIPTION}", ""); + } + r.setText(text, 0); + } else if(text.equals("{ARGOS.DMP.RESEARCHERS}")) { + String researchersNames = ""; + int i = 0; + for(Reference researcher : researchers){ + i++; + researchersNames += researcher.getLabel() + (i < researchers.size() ? ", " : ""); + } + text = text.replace("{ARGOS.DMP.RESEARCHERS}", researchersNames); + r.setText(text, 0); + r.setFontSize(17); + } else if(text.equals("{ARGOS.DMP.ORGANIZATIONS}")) { + String organisationsNames = ""; + int i = 0; + for(Reference organisation : organizations){ + i++; + organisationsNames += organisation.getLabel() + (i < organizations.size() ? ", " : ""); + } + text = text.replace("{ARGOS.DMP.ORGANIZATIONS}", organisationsNames); + r.setText(text, 0); + r.setFontSize(17); + } + } + } + } + parPos++; + } + + if((descrParPos != -1) && (dmpEntity!=null) && (dmpEntity.getDescription() != null) && !isDataset) { + XmlCursor cursor = descrPar.getCTP().newCursor(); + cursor.toNextSibling(); + Document htmlDoc = Jsoup.parse(((String)dmpEntity.getDescription()).replaceAll("\n", "
")); + HtmlToWorldBuilder htmlToWorldBuilder = new HtmlToWorldBuilder(descrPar, 0, cursor); + NodeTraversor.traverse(htmlToWorldBuilder, htmlDoc); + } + if((descrParPos != -1) && (descriptionFileTransformerModelEntityEntity != null) && (descriptionFileTransformerModelEntityEntity.getDescription() != null) && isDataset) { + XmlCursor cursor = descrPar.getCTP().newCursor(); + cursor.toNextSibling(); + Document htmlDoc = Jsoup.parse(((String) descriptionFileTransformerModelEntityEntity.getDescription()).replaceAll("\n", "
")); + HtmlToWorldBuilder htmlToWorldBuilder = new HtmlToWorldBuilder(descrPar, 0, cursor); + NodeTraversor.traverse(htmlToWorldBuilder, htmlDoc); + } + + + XWPFTable tbl = document.getTables().get(0); + Iterator it = tbl.getRows().iterator(); + it.next(); // skip first row + if(it.hasNext() && !funders.isEmpty()){ + XWPFParagraph p = it.next().getCell(0).getParagraphs().get(0); + XWPFRun run = p.createRun(); + run.setText(funders.get(0).getLabel()); + run.setFontSize(17); + p.setAlignment(ParagraphAlignment.CENTER); + } + it = tbl.getRows().iterator(); + it.next(); + + if(it.hasNext() && !grants.isEmpty()){ + XWPFParagraph p = it.next().getCell(1).getParagraphs().get(0); + XWPFRun run = p.createRun(); + String text = grants.get(0).getLabel(); + String reference = grants.get(0).getReference(); + if(reference != null) { + String[] parts = reference.split("::"); + text += parts.length > 1 ? "/ No "+parts[parts.length - 1] : ""; + } + run.setText(text); + run.setFontSize(17); + p.setAlignment(ParagraphAlignment.CENTER); + } + } + + public void fillFooter(DmpFileTransformerModel dmpEntity, DescriptionFileTransformerModel descriptionFileTransformerModelEntityEntity, XWPFDocument document, boolean isDataset) { + document.getFooterList().forEach(xwpfFooter -> { + List runs = xwpfFooter.getParagraphs().get(0).getRuns(); + if(runs != null){ + for(XWPFRun r : runs){ + String text = r.getText(0); + if(text != null){ + if(text.contains("{ARGOS.DMP.TITLE}")){ + text = text.replace("{ARGOS.DMP.TITLE}", dmpEntity.getLabel()); + r.setText(text, 0); + } + if(text.contains("{ARGOS.DATASET.TITLE}") && descriptionFileTransformerModelEntityEntity != null){ + text = text.replace("{ARGOS.DATASET.TITLE}", descriptionFileTransformerModelEntityEntity.getLabel()); + r.setText(text, 0); + } + if(text.contains("{ARGOS.DMP.LICENSE}")){ + try{ + Map license = ((Map) mapper.readValue(dmpEntity.getProperties(), Map.class).get("license")); + text = text.replace("{ARGOS.DMP.LICENSE}", license.get("pid")); + } + catch (JsonProcessingException | NullPointerException e){ + text = text.replace("{ARGOS.DMP.LICENSE}", "License: -"); + } + r.setText(text, 0); + } + if(text.contains("{ARGOS.DMP.DOI}")){ + if(dmpEntity.getEntityDois() != null && !dmpEntity.getEntityDois().isEmpty()) + text = text.replace("{ARGOS.DMP.DOI}", dmpEntity.getEntityDois().iterator().next().getDoi()); + else + text = text.replace("{ARGOS.DMP.DOI}", "-"); + r.setText(text, 0); + } + if(text.contains("{ARGOS.DMP.LAST_MODIFIED}")){ + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy").withZone(ZoneId.systemDefault()); + text = text.replace("{ARGOS.DMP.LAST_MODIFIED}", formatter.format(dmpEntity.getUpdatedAt())); + r.setText(text, 0); + } + } + } + } + }); + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/word/XWPFHtmlDocument.java b/core/src/main/java/eu/eudat/file/transformer/utils/word/XWPFHtmlDocument.java new file mode 100644 index 0000000..f1855e3 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/word/XWPFHtmlDocument.java @@ -0,0 +1,63 @@ +package eu.eudat.file.transformer.utils.word; + +import org.apache.poi.ooxml.POIXMLDocumentPart; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackagePartName; +import org.apache.poi.openxml4j.opc.PackagingURIHelper; +import org.apache.poi.xwpf.usermodel.XWPFDocument; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.UUID; + +public class XWPFHtmlDocument extends POIXMLDocumentPart { + + private String html; + private String id; + + public XWPFHtmlDocument(PackagePart pkg, String id) { + super(pkg); + this.html = "HTML import

"; + this.id = id; + } + + public String getHtml() { + return html; + } + + public void setHtml(String html) { + this.html = this.html.replace("

", html); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + protected void commit() throws IOException { + PackagePart packagePart = getPackagePart(); + OutputStream outputStream = packagePart.getOutputStream(); + Writer writer = new OutputStreamWriter(outputStream, "UTF-8"); + writer.write(html); + writer.close(); + outputStream.close(); + } + + public static XWPFHtmlDocument addHtmlDocument(XWPFDocument document) throws InvalidFormatException { + OPCPackage oPCPackage = document.getPackage(); + String id = UUID.randomUUID().toString(); + PackagePartName partName = PackagingURIHelper.createPartName("/word/" + id + ".html"); + PackagePart part = oPCPackage.createPart(partName, "text/html"); + XWPFHtmlDocument xWPFHtmlDocument = new XWPFHtmlDocument(part, id); + document.addRelation(xWPFHtmlDocument.getId(), new XWPFHtmlRelation(), xWPFHtmlDocument); + return xWPFHtmlDocument; + } +} diff --git a/core/src/main/java/eu/eudat/file/transformer/utils/word/XWPFHtmlRelation.java b/core/src/main/java/eu/eudat/file/transformer/utils/word/XWPFHtmlRelation.java new file mode 100644 index 0000000..2f90057 --- /dev/null +++ b/core/src/main/java/eu/eudat/file/transformer/utils/word/XWPFHtmlRelation.java @@ -0,0 +1,11 @@ +package eu.eudat.file.transformer.utils.word; + +import org.apache.poi.ooxml.POIXMLRelation; + +public class XWPFHtmlRelation extends POIXMLRelation { + public XWPFHtmlRelation() { + super("text/html", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/aFChunk", + "/word/htmlDoc#.html"); + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..c3c4963 --- /dev/null +++ b/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.0 + + + + gr.cite.opendmp + file-transformer-document-parent + ${revision} + pom + + + 21 + 21 + 21 + 1.0.0-SNAPSHOT + + + + core + web + + + diff --git a/web/pom.xml b/web/pom.xml new file mode 100644 index 0000000..7da0e7b --- /dev/null +++ b/web/pom.xml @@ -0,0 +1,59 @@ + + + 4.0.0 + + gr.cite.opendmp + file-transformer-document-parent + ${revision} + .. + + + file-transformer-document-web + ${revision} + jar + + + 21 + 21 + 21 + 1.0.0-SNAPSHOT + + + + + gr.cite.opendmp + file-transformer-document + ${revision} + + + org.springframework.boot + spring-boot-starter-web + + + gr.cite + oidc-authn + 2.1.0 + + + gr.cite + cache + 2.1.0 + + + org.springframework.boot + spring-boot-starter-cache + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 3.1.0 + + + + + diff --git a/web/src/main/java/eu/eudat/file/transformer/FileTransformerApplication.java b/web/src/main/java/eu/eudat/file/transformer/FileTransformerApplication.java new file mode 100644 index 0000000..b08edb9 --- /dev/null +++ b/web/src/main/java/eu/eudat/file/transformer/FileTransformerApplication.java @@ -0,0 +1,16 @@ +package eu.eudat.file.transformer; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = { + "eu.eudat.file.transformer.*", + "gr.cite.tools", + "gr.cite.commons" +}) +public class FileTransformerApplication { + + public static void main(String[] args) { + SpringApplication.run(FileTransformerApplication.class, args); + } +} diff --git a/web/src/main/java/eu/eudat/file/transformer/config/SecurityConfiguration.java b/web/src/main/java/eu/eudat/file/transformer/config/SecurityConfiguration.java new file mode 100644 index 0000000..d0269e8 --- /dev/null +++ b/web/src/main/java/eu/eudat/file/transformer/config/SecurityConfiguration.java @@ -0,0 +1,76 @@ +package eu.eudat.file.transformer.config; + +import gr.cite.commons.web.oidc.configuration.WebSecurityProperties; +import gr.cite.commons.web.oidc.configuration.filter.ApiKeyFilter; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManagerResolver; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; + +import java.util.Set; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration { + + private final ApiKeyFilter apiKeyFilter; + private final WebSecurityProperties webSecurityProperties; + private final AuthenticationManagerResolver authenticationManagerResolver; + + @Autowired + public SecurityConfiguration(ApiKeyFilter apiKeyFilter, WebSecurityProperties webSecurityProperties, AuthenticationManagerResolver authenticationManagerResolver) { + this.apiKeyFilter = apiKeyFilter; + this.webSecurityProperties = webSecurityProperties; + this.authenticationManagerResolver = authenticationManagerResolver; + } + + @Bean + protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + if (webSecurityProperties.isEnabled()) { + http.csrf(AbstractHttpConfigurer::disable) + .cors(Customizer.withDefaults()) + .addFilterBefore(apiKeyFilter, AbstractPreAuthenticatedProcessingFilter.class) + .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry + .requestMatchers(buildAntPatterns(webSecurityProperties.getAuthorizedEndpoints())).authenticated() + .requestMatchers(buildAntPatterns(webSecurityProperties.getAllowedEndpoints())).anonymous()) + .sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.NEVER)) + .oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver)); + return http.build(); + } else { + return http.csrf(AbstractHttpConfigurer::disable) + .cors(Customizer.withDefaults()) + .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> + authorizationManagerRequestMatcherRegistry.anyRequest().anonymous()) + .build(); + } + } + + private String[] buildAntPatterns(Set endpoints) { + if (endpoints == null) { + return new String[0]; + } + return endpoints.stream() + .filter(endpoint -> endpoint != null && !endpoint.isBlank()) + .map(endpoint -> "/" + stripUnnecessaryCharacters(endpoint) + "/**") + .toArray(String[]::new); + } + + private String stripUnnecessaryCharacters(String endpoint) { + endpoint = endpoint.strip(); + if (endpoint.startsWith("/")) { + endpoint = endpoint.substring(1); + } + if (endpoint.endsWith("/")) { + endpoint = endpoint.substring(0, endpoint.length() - 1); + } + return endpoint; + } +} diff --git a/web/src/main/java/eu/eudat/file/transformer/controller/FileTransformerController.java b/web/src/main/java/eu/eudat/file/transformer/controller/FileTransformerController.java new file mode 100644 index 0000000..4e2314d --- /dev/null +++ b/web/src/main/java/eu/eudat/file/transformer/controller/FileTransformerController.java @@ -0,0 +1,53 @@ +package eu.eudat.file.transformer.controller; + +import eu.eudat.file.transformer.executor.FileTransformerExecutor; +import eu.eudat.file.transformer.model.DescriptionFileTransformerModel; +import eu.eudat.file.transformer.model.DmpFileTransformerModel; +import eu.eudat.file.transformer.model.ExtraPropertiesModel; +import eu.eudat.file.transformer.model.enums.FileFormats; +import eu.eudat.file.transformer.model.file.FileEnvelope; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.*; + +@RestController +@RequestMapping("/api/file") +public class FileTransformerController { + + private final FileTransformerExecutor fileTransformerExecutor; + + @Autowired + public FileTransformerController(FileTransformerExecutor fileTransformerExecutor) { + this.fileTransformerExecutor = fileTransformerExecutor; + } + + @PostMapping("/export/dmp") + public FileEnvelope exportDmp(@RequestBody DmpFileTransformerModel dmpDepositModel, @RequestParam(value = "format",required = false)String format) throws Exception { + ExtraPropertiesModel properties = new ExtraPropertiesModel(); + properties.setFormat(format != null ? format : "docx"); + return fileTransformerExecutor.exportDmp(dmpDepositModel, properties); + } + + @PostMapping("/export/description") + public FileEnvelope exportDescription(@RequestBody DescriptionFileTransformerModel descriptionFileTransformerModel, @RequestParam(value = "format",required = false)String format, @RequestParam(value = "descriptionId",required = false) String descriptionId) throws Exception { + ExtraPropertiesModel properties = new ExtraPropertiesModel(); + properties.setFormat(format != null ? format : "docx"); + return fileTransformerExecutor.exportDescription(descriptionFileTransformerModel, properties); + } + + @PostMapping("/import/dmp") + public DmpFileTransformerModel importFileToDmp(@RequestBody FileEnvelope fileEnvelope) { + return fileTransformerExecutor.importFileToDmp(fileEnvelope); + } + + @PostMapping("/import/description") + public DescriptionFileTransformerModel importFileToDescription(@RequestBody FileEnvelope fileEnvelope) { + return fileTransformerExecutor.importFileToDescription(fileEnvelope); + } + + @GetMapping("/formats") + public List getSupportedFormats() { + return fileTransformerExecutor.getSupportedFileFormats(); + } +} diff --git a/web/src/main/resources/config/application.yml b/web/src/main/resources/config/application.yml new file mode 100644 index 0000000..9bb90cb --- /dev/null +++ b/web/src/main/resources/config/application.yml @@ -0,0 +1,11 @@ +spring: + jackson: + default-property-inclusion: non_null + config: + import: optional:classpath:config/app.env[.properties], optional:file:../config/app.env[.properties], + optional:classpath:config/server.yml[.yml], optional:classpath:config/server-${spring.profiles.active}.yml[.yml], optional:file:../config/server-${spring.profiles.active}.yml[.yml], + optional:classpath:config/storage.yml[.yml], optional:classpath:config/storage-${spring.profiles.active}.yml[.yml], optional:file:../config/storage-${spring.profiles.active}.yml[.yml], + optional:classpath:config/security.yml[.yml], optional:classpath:config/security-${spring.profiles.active}.yml[.yml], optional:file:../config/security-${spring.profiles.active}.yml[.yml], + optional:classpath:config/cache.yml[.yml], optional:classpath:config/cache-${spring.profiles.active}.yml[.yml], optional:file:../config/cache-${spring.profiles.active}.yml[.yml], + optional:classpath:config/pdf.yml[.yml], optional:classpath:config/pdf-${spring.profiles.active}.yml[.yml], optional:file:../config/pdf-${spring.profiles.active}.yml[.yml] + diff --git a/web/src/main/resources/config/cache.yml b/web/src/main/resources/config/cache.yml new file mode 100644 index 0000000..005e6de --- /dev/null +++ b/web/src/main/resources/config/cache.yml @@ -0,0 +1,16 @@ +cache: + manager: + fallbackToNoOpCache: true + caffeineCaches: + - names: [ "apikey" ] + allowNullValues: true + initialCapacity: 100 + maximumSize: 500 + enableRecordStats: false + expireAfterWriteMinutes: 10 + expireAfterAccessMinutes: 10 + refreshAfterWriteMinutes: 10 + mapCaches: + apiKey: + name: apikey + keyPattern: resolve_$keyhash$:v0 \ No newline at end of file diff --git a/web/src/main/resources/config/pdf.yml b/web/src/main/resources/config/pdf.yml new file mode 100644 index 0000000..64f4ae7 --- /dev/null +++ b/web/src/main/resources/config/pdf.yml @@ -0,0 +1,3 @@ +pdf: + converter: + url: ${PDF_CONVERTER_URL:} \ No newline at end of file diff --git a/web/src/main/resources/config/security.yml b/web/src/main/resources/config/security.yml new file mode 100644 index 0000000..ad38bd8 --- /dev/null +++ b/web/src/main/resources/config/security.yml @@ -0,0 +1,20 @@ +web: + security: + enabled: true + authorized-endpoints: [ api ] + allowed-endpoints: [ health ] + idp: + api-key: + enabled: true + authorization-header: Authorization + client-id: ${IDP_APIKEY_CLIENT_ID:} + client-secret: ${IDP_APIKEY_CLIENT_SECRET:} + scope: ${IDP_APIKEY_SCOPE:} + resource: + token-type: JWT #| opaque + opaque: + client-id: ${IDP_OPAQUE_CLIENT_ID:} + client-secret: ${IDP_OPAQUE_CLIENT_SECRET:} + jwt: + claims: [ role, x-role ] + issuer-uri: ${IDP_ISSUER_URI:} \ No newline at end of file diff --git a/web/src/main/resources/config/server.yml b/web/src/main/resources/config/server.yml new file mode 100644 index 0000000..09fa334 --- /dev/null +++ b/web/src/main/resources/config/server.yml @@ -0,0 +1,12 @@ +server: + port: 8084 + tomcat: + threads: + max: 20 + max-connections: 10000 + +spring: + servlet: + multipart: + max-file-size: 10MB + max-request-size: 10MB \ No newline at end of file diff --git a/web/src/main/resources/config/storage.yml b/web/src/main/resources/config/storage.yml new file mode 100644 index 0000000..1372c34 --- /dev/null +++ b/web/src/main/resources/config/storage.yml @@ -0,0 +1,7 @@ +file: + template: + word-template: classpath:documents/h2020.docx + pid-template: classpath:pidLinks.json + word-description-template: classpath:documents/h2020_dataset.docx + storage: + temp: ${TEMP_PATH} \ No newline at end of file diff --git a/web/src/main/resources/documents/h2020.docx b/web/src/main/resources/documents/h2020.docx new file mode 100644 index 0000000000000000000000000000000000000000..b0e721ab0234053b1488fd2d6b2b8af8a066d1c2 GIT binary patch literal 30266 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⋘GtWx7yn2#=O-m5*C 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 AsHFdC@ml^`Eug$wO|*OI~H)i|r3LwTnjdUxIC zyC(z$PGkBi#1qd%ea^HI=IS=q1(*x8cv7kJ7E>|UhSaM)mlB~o-elbfguDN1uE)&3 zfcFNK9D(v34P>MSa!>pDPKeRK(&lHXgI2!yuX`G(k#$uV_~D+`NPdD6d6b{*a~hyP zPP`i^A%^K$hv#C7RAVrAxOZgzjUyYvdSNl1=wKLN+cZt9>2+4YPOgLq*WY@qBF0LP zzO}>EA{37@2$!P)NnQmWUb3?z-eEDM2^(Hgz2XIxDonZd0U&BZ>{ZNs>580i&2Jb7 z=UydKiK@ui5p%-G0jY&<6UGkpLQS_0W6+{Bz&^_Bdz3v;2_2Qt8v^NtqZT59>k5q3 z{Nr3i1c}tkj_&r<$=9=$Gdh=@Q)cXH%7OS>jK_QHK$_wN$qA<}@G+6&SG{SUIId8; zdl{GBc=}s>mDL|4DjR)u!|-L?Cgs?zxC7epFbnBKZecT1fY)E2Pil38k0M|JsYyO# z=!MO2qxrFA-JVt1=SASoF}?gvw5ttD6Olht0L1gUiq5CV5Dhi|c=-@_NnLcIh zh=hn*Kx5{EciNKlaf|+!=)xqWiHd^4RW)nI2wwTYLF|)o>bSdWny=%r<03tb5-tCl zxmj15Cw8EcUQp(u{+_vdR#rdS=>H}zC~HBVq+f|Ee^@+$P~gQ;B1hNhDVh!AT$3cN z51frTP&q%EV?*_-i<}rPa^U4*F5=TjCLDt`l1>@UQBO#VtWnAto|sv6uaJsH^`-Hx zfjK+(A=5q?csA?2H!A8&Mh1^@S`|39$bxO?T@0saMzE_iUHcsUoV`*xn*vb8TV?V* z@%@|^P*g}!pWE77cb;yEc+Eptl8(m%Sj@#ez9y%;R~68k+8~Z(PMTIGzb#PRyA`~L z_YnqBQ{G_3R@V*OWlyqQQr8W7PsEXoysPJZFr6+rqQkH$rEr$+b#&AcjAqyR#y-c+ zre|n4m_?bO(=ly|BaAsBP#&*7b?^cj?=0i8o&CLyk1myOH~YabT{X&B zy0^^_H)Z;$FDFS!@zNU#)xEf4`>54*ypy6!JHAQdQIT6DS11cUZFNoljXC7 znDvNQ5qXc}a(l}~M@_4t?6cI;`O2|#*ykb3VM3F$VXUhu0@A`qaicOhGVC`fk?ou+ zqpv?0GKjgnYFg$P-=rLqbzH-&)^kGSt0Wz&0D~@RK~S6#TpCC62X+c8`rKuwmUMLO zKI5ub+=ujEo7Zt8+i0fy9LgK7PPtqyAIM2ey71gFIY^iBVS@OwrfJ15DpNma$BIMZ z)wn&NJJk8G4_ih#q-Ce7^X}~#vKv;01cH{SbIwRdB9K4)P3@r!K-dqd36l2^h}4E9 zZ7~V5a)*9dZnk`MB;|N--^cl{S!nl$+qoT7LJi79Vo(oBBUGnE#rl{>n$N zkdg8rklY1k<$h(U&BQY4n_NFyt|W~ej|6f(zNomA!GM~X>2mXC4xQaLb>QQzd_$A3 zur_mLu1Q+HnH(+=5}yo=`|Z6MWKdUk8w2K8D-mAJ;+CjOXO{&0$v#G5h7bo+aF%89 z+z>9k>Sv~y0Woeh8iN$OibHd%$#)-{rmtJT7o+o$y**uV@#XGc19>JNm{*`XmJ9NO;OjqJOn5AUTARPxB5;V8py$MYU()cms{Y*k zmjw>xB>yD%bD6Z?2@XJq{B6;+r@*HRM*V^kg48!Z-_ZYV-uo2(r|0i4Fc?@INS^Z( z{=YZ?KV^FA(fW&vh462tKO9@1;-8vd{=(ydR+jsDCcj%@K7~KE75oK%Ap7m`f136QqJWBsvi$Ant zPw`JRD!=ew<^OXP{ztL$6#R4$=ok2l{@>syLqbm(o(^;TVz@B)o52{g+UfrcdOXGd z`RMoy4O&3a7!2$m50X#efA-&hhbNl;2LHP^{}laapXztCu=#K3zix6rdRR{h{=6Oj hPA~xYJq7;l<|ro#{lfTK`#(BWS%?4t literal 0 HcmV?d00001 diff --git a/web/src/main/resources/documents/h2020_dataset.docx b/web/src/main/resources/documents/h2020_dataset.docx new file mode 100644 index 0000000000000000000000000000000000000000..4963e3c18449df7981886f8ea392fecc25523d39 GIT binary patch literal 20315 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 literal 0 HcmV?d00001 diff --git a/web/src/main/resources/documents/styles.xml b/web/src/main/resources/documents/styles.xml new file mode 100644 index 0000000..4e78e69 --- /dev/null +++ b/web/src/main/resources/documents/styles.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/web/src/main/resources/pidLinks.json b/web/src/main/resources/pidLinks.json new file mode 100644 index 0000000..d2a0116 --- /dev/null +++ b/web/src/main/resources/pidLinks.json @@ -0,0 +1,100 @@ +{ + "pidLinks": [ + { + "pid": "doi", + "link": "https://doi.org/{pid}" + }, + { + "pid": "uniprot", + "link": "https://uniprot.org/uniprotkb/{pid}" + }, + { + "pid": "handle", + "link": "https://hdl.handle.net/{pid}" + }, + { + "pid": "arxiv", + "link": "https://arxiv.org/abs/{pid}" + }, + { + "pid": "ascl", + "link": "https://ascl.net/{pid}" + }, + { + "pid": "orcid", + "link": "https://orcid.org/{pid}" + }, + { + "pid": "pmid", + "link": "https://pubmed.ncbi.nlm.nih.gov/{pid}" + }, + { + "pid": "ads", + "link": "https://ui.adsabs.harvard.edu/#abs/{pid}" + }, + { + "pid": "pmcid", + "link": "https://ncbi.nlm.nih.gov/pmc/{pid}" + }, + { + "pid": "gnd", + "link": "https://d-nb.info/gnd/{pid}" + }, + { + "pid": "urn", + "link": "https://nbn-resolving.org/{pid}" + }, + { + "pid": "sra", + "link": "https://ebi.ac.uk/ena/data/view/{pid}" + }, + { + "pid": "bioproject", + "link": "https://ebi.ac.uk/ena/data/view/{pid}" + }, + { + "pid": "biosample", + "link": "https://ebi.ac.uk/ena/data/view/{pid}" + }, + { + "pid": "ensembl", + "link": "https://ensembl.org/id/{pid}" + }, + { + "pid": "refseq", + "link": "https://ncbi.nlm.nih.gov/entrez/viewer.fcgi?val={pid}" + }, + { + "pid": "genome", + "link": "https://ncbi.nlm.nih.gov/assembly/{pid}" + }, + { + "pid": "geo", + "link": "https://ncbi.nlm.nih.gov/geo/query/acc.cgi?acc={pid}" + }, + { + "pid": "arrayexpress_array", + "link": "https://ebi.ac.uk/arrayexpress/arrays/{pid}" + }, + { + "pid": "arrayexpress_experiment", + "link": "https://ebi.ac.uk/arrayexpress/experiments/{pid}" + }, + { + "pid": "hal", + "link": "https://hal.archives-ouvertes.fr/{pid}" + }, + { + "pid": "swh", + "link": "https://archive.softwareheritage.org/{pid}" + }, + { + "pid": "ror", + "link": "https://ror.org/{pid}" + }, + { + "pid": "viaf", + "link": "https://viaf.org/viaf/{pid}" + } + ] +} \ No newline at end of file