From 6f2d16c2b991cc47e57b8d0346a594dbd9036d76 Mon Sep 17 00:00:00 2001 From: widlam Date: Mon, 4 Dec 2023 06:59:05 +0100 Subject: [PATCH] Added backend support for multiple files in XSLT. (#272) Co-authored-by: Adam Bem Reviewed-on: https://gitea.release11.com/R11/release11-tools/pulls/272 Reviewed-by: Adam Bem Co-authored-by: widlam Co-committed-by: widlam --- .../java/com/r11/tools/SparkApplication.java | 1 + .../controller/MultipleXMLController.java | 96 +++++++++++++++++++ .../controller/internal/MultipleXmlJob.java | 36 +++++++ .../r11/tools/model/XMLMultipleFilesBody.java | 32 +++++++ .../r11/tools/model/XMLMultipleFilesData.java | 18 ++++ .../main/java/com/r11/tools/xml/Saxon.java | 67 ++++++++++++- .../main/java/com/r11/tools/xml/Xalan.java | 11 ++- .../java/com/r11/tools/xml/XmlEngine.java | 3 + .../xml/XmlOutputFieldComponent.vue | 1 + 9 files changed, 262 insertions(+), 3 deletions(-) create mode 100644 Backend/tools-services/src/main/java/com/r11/tools/controller/MultipleXMLController.java create mode 100644 Backend/tools-services/src/main/java/com/r11/tools/controller/internal/MultipleXmlJob.java create mode 100644 Backend/tools-services/src/main/java/com/r11/tools/model/XMLMultipleFilesBody.java create mode 100644 Backend/tools-services/src/main/java/com/r11/tools/model/XMLMultipleFilesData.java diff --git a/Backend/tools-services/src/main/java/com/r11/tools/SparkApplication.java b/Backend/tools-services/src/main/java/com/r11/tools/SparkApplication.java index 93d5828..ce40a6b 100644 --- a/Backend/tools-services/src/main/java/com/r11/tools/SparkApplication.java +++ b/Backend/tools-services/src/main/java/com/r11/tools/SparkApplication.java @@ -42,6 +42,7 @@ public class SparkApplication { registry.registerController(new ProcessorInfoController(logger, saxon, xalan)); registry.registerController(new XmlController(gson, logger, saxon, xalan)); + registry.registerController(new MultipleXMLController(gson,logger, saxon)); registry.registerController(new JsonController(gson, jsongson, logger)); registry.register(); diff --git a/Backend/tools-services/src/main/java/com/r11/tools/controller/MultipleXMLController.java b/Backend/tools-services/src/main/java/com/r11/tools/controller/MultipleXMLController.java new file mode 100644 index 0000000..b6c24bb --- /dev/null +++ b/Backend/tools-services/src/main/java/com/r11/tools/controller/MultipleXMLController.java @@ -0,0 +1,96 @@ +package com.r11.tools.controller; + +import com.google.gson.Gson; +import com.r11.tools.controller.internal.*; +import com.r11.tools.model.XMLMultipleFilesBody; +import com.r11.tools.model.XMLResponseBody; +import com.r11.tools.xml.XmlEngine; +import org.apache.logging.log4j.Logger; +import spark.Request; +import spark.Response; + +@GlobalControllerManifest +public class MultipleXMLController implements RestController { + + private final Gson gson; + private final Logger logger; + + private final XmlEngine engine; + + public MultipleXMLController(Gson gson, Logger logger, XmlEngine engine) { + this.gson = gson; + this.logger = logger; + this.engine = engine; + } + + @ScopedControllerManifest(method = HandlerType.POST, path = "/multiple/xslt") + public void acceptRequestXslt(Request request, Response response) { + acceptRequest(request, response, XmlJobType.XSLT); + } + + private void acceptRequest(Request request, Response response, XmlJobType xmlJobType) { + XMLMultipleFilesBody requestBody; + try { + requestBody = this.gson.fromJson(request.body(), XMLMultipleFilesBody.class); + } catch (Exception e) { + requestErrorResponse(response, e); + return; + } + processRequest(new MultipleXmlJob(response, requestBody, engine, xmlJobType)); + } + + private void processRequest(MultipleXmlJob xmlJob) { + XMLResponseBody responseBody = null; + long timeStart = System.currentTimeMillis(); + long duration; + + try { + responseBody = processData(xmlJob); + + duration = System.currentTimeMillis() - timeStart; + responseBody.setDuration(duration); + + xmlJob.getResponse().status(200); + + this.logger.info("Request (" + xmlJob.getXmlJobType() + ", " + + xmlJob.getEngine().getVersion() + + ") processed in " + duration + " ms."); + + } catch (Exception ex) { + responseBody = processingErrorResponse(ex, xmlJob); + + } finally { + xmlJob.getResponse().body(this.gson.toJson(responseBody)); + } + + } + + private XMLResponseBody processData(MultipleXmlJob xmlJob) throws Exception { + XmlEngine engine = xmlJob.getEngine(); + XMLMultipleFilesBody requestBody = xmlJob.getRequestBody(); + String result = engine.processXSLT(requestBody.getData(), requestBody.getProcessorData()); + return new XMLResponseBody(result, "OK", requestBody.getVersion()); + } + + + private XMLResponseBody processingErrorResponse(Exception ex, MultipleXmlJob xmlJob) { + XmlEngine engine = xmlJob.getEngine(); + XmlJobType xmlJobType = xmlJob.getXmlJobType(); + Response response = xmlJob.getResponse(); + + XMLResponseBody responseBody = + new XMLResponseBody(ex.getMessage(), "ERR", engine.getVersion(), -1); + + response.status(400); + this.logger.error("Error on processing " + xmlJobType + " using " + engine.getVersion() + ". " + ex); + + return responseBody; + } + + private void requestErrorResponse(Response response, Exception ex) { + XMLResponseBody responseBody = new XMLResponseBody(ex.getMessage(), "ERR", "N/A", -1); + response.status(400); + response.body(this.gson.toJson(responseBody)); + } + +} diff --git a/Backend/tools-services/src/main/java/com/r11/tools/controller/internal/MultipleXmlJob.java b/Backend/tools-services/src/main/java/com/r11/tools/controller/internal/MultipleXmlJob.java new file mode 100644 index 0000000..c896188 --- /dev/null +++ b/Backend/tools-services/src/main/java/com/r11/tools/controller/internal/MultipleXmlJob.java @@ -0,0 +1,36 @@ +package com.r11.tools.controller.internal; + +import com.r11.tools.model.XMLMultipleFilesBody; +import com.r11.tools.xml.XmlEngine; +import spark.Response; + +public class MultipleXmlJob { + + private final Response response; + private final XMLMultipleFilesBody requestBody; + private final XmlEngine engine; + private final XmlJobType xmlJobType; + + public MultipleXmlJob(Response response, XMLMultipleFilesBody requestBody, XmlEngine engine, XmlJobType xmlJobType) { + this.response = response; + this.requestBody = requestBody; + this.engine = engine; + this.xmlJobType = xmlJobType; + } + + public Response getResponse() { + return response; + } + + public XMLMultipleFilesBody getRequestBody() { + return requestBody; + } + + public XmlEngine getEngine() { + return engine; + } + + public XmlJobType getXmlJobType() { + return xmlJobType; + } +} diff --git a/Backend/tools-services/src/main/java/com/r11/tools/model/XMLMultipleFilesBody.java b/Backend/tools-services/src/main/java/com/r11/tools/model/XMLMultipleFilesBody.java new file mode 100644 index 0000000..67291a4 --- /dev/null +++ b/Backend/tools-services/src/main/java/com/r11/tools/model/XMLMultipleFilesBody.java @@ -0,0 +1,32 @@ +package com.r11.tools.model; + +import com.google.gson.annotations.SerializedName; + +public class XMLMultipleFilesBody { + + @SerializedName("data") + private XMLMultipleFilesData[] data; + @SerializedName("processorData") + private String processorData; + @SerializedName("processor") + private String processor; + @SerializedName("version") + private String version; + + + public String getProcessorData() { + return processorData; + } + + public String getProcessor() { + return processor; + } + + public String getVersion() { + return version; + } + + public XMLMultipleFilesData[] getData() { + return data; + } +} diff --git a/Backend/tools-services/src/main/java/com/r11/tools/model/XMLMultipleFilesData.java b/Backend/tools-services/src/main/java/com/r11/tools/model/XMLMultipleFilesData.java new file mode 100644 index 0000000..9dd4743 --- /dev/null +++ b/Backend/tools-services/src/main/java/com/r11/tools/model/XMLMultipleFilesData.java @@ -0,0 +1,18 @@ +package com.r11.tools.model; + +import com.google.gson.annotations.SerializedName; + +public class XMLMultipleFilesData { + @SerializedName("fileName") + private String filename; + @SerializedName("fileData") + private String data; + + public String getFilename() { + return filename; + } + + public String getData() { + return data; + } +} diff --git a/Backend/tools-services/src/main/java/com/r11/tools/xml/Saxon.java b/Backend/tools-services/src/main/java/com/r11/tools/xml/Saxon.java index 9237c2e..f610a9e 100644 --- a/Backend/tools-services/src/main/java/com/r11/tools/xml/Saxon.java +++ b/Backend/tools-services/src/main/java/com/r11/tools/xml/Saxon.java @@ -1,11 +1,16 @@ package com.r11.tools.xml; +import com.r11.tools.model.XMLMultipleFilesData; import com.r11.tools.model.XPathQueryResult; import net.sf.saxon.s9api.*; import javax.xml.transform.stream.StreamSource; -import java.io.StringReader; -import java.io.StringWriter; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Comparator; +import java.util.UUID; /** * Handler for Saxon engine @@ -13,6 +18,64 @@ import java.io.StringWriter; */ public class Saxon implements XmlEngine{ + /** + * Transforms many XML documents via XSLT. + * @param data XML Files to be transformed. + * @param transform XSLT + * @return transformed xml + * @throws SaxonApiException thrown on stylesheet or transformation error + * @throws IOException thrown when file does not exist, or cannot be read. + */ + public String processXSLT(XMLMultipleFilesData[] data, String transform) throws SaxonApiException, IOException{ + Processor processor = new Processor(false); + XsltCompiler compiler = processor.newXsltCompiler(); + + String filesPath = "/tmp/"+UUID.randomUUID()+"/"; + try{ + createXMLFilesFromData(data, filesPath); + Path transformPath = createXSLTFileAndReturnPath(transform,filesPath); + XsltExecutable stylesheet = compiler.compile( new StreamSource( transformPath.toFile() )); + + StringWriter sw = new StringWriter(); + Serializer out = processor.newSerializer(sw); + out.setOutputProperty(Serializer.Property.METHOD, "xml"); + out.setOutputProperty(Serializer.Property.INDENT, "yes"); + Xslt30Transformer transformer = stylesheet.load30(); + transformer.transform( new StreamSource( new File(filesPath+data[0].getFilename()) ) , out ); + return sw.toString(); + } finally { + deleteTemporaryFiles(filesPath); + } + + } + + private void createXMLFilesFromData( XMLMultipleFilesData[] data , String filesPath ) throws IOException { + Files.createDirectories(Paths.get(filesPath)); + for (XMLMultipleFilesData fileData : data) { + Path filePath = Files.createFile( Paths.get(filesPath + fileData.getFilename() ) ); + try (FileWriter writer = new FileWriter(filePath.toFile())) { + writer.write(fileData.getData()); + } + } + } + + private Path createXSLTFileAndReturnPath( String xsltTransform, String filesPath ) throws IOException { + Path transformPath = Files.createFile( Paths.get(filesPath + "transform.xsl") ); + FileWriter writer = new FileWriter(transformPath.toFile()); + writer.write(xsltTransform); + writer.close(); + + return transformPath; + } + + private void deleteTemporaryFiles(String filesPath) throws IOException { + Files + .walk( Paths.get(filesPath) ) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + /** * Transforms string containing xml document via xslt * @param data xml to be transformed diff --git a/Backend/tools-services/src/main/java/com/r11/tools/xml/Xalan.java b/Backend/tools-services/src/main/java/com/r11/tools/xml/Xalan.java index befbe23..5c64093 100644 --- a/Backend/tools-services/src/main/java/com/r11/tools/xml/Xalan.java +++ b/Backend/tools-services/src/main/java/com/r11/tools/xml/Xalan.java @@ -1,5 +1,6 @@ package com.r11.tools.xml; +import com.r11.tools.model.XMLMultipleFilesData; import com.r11.tools.model.XPathQueryResult; import org.apache.xpath.XPathAPI; import org.w3c.dom.Document; @@ -17,7 +18,10 @@ import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.OutputStreamWriter; +import java.io.StringReader; +import java.io.StringWriter; /** * Handler for Xalan engine @@ -57,6 +61,11 @@ public class Xalan implements XmlEngine{ return nodeType == Node.CDATA_SECTION_NODE || nodeType == Node.TEXT_NODE; } + @Override + public String processXSLT(XMLMultipleFilesData[] data, String transform) throws Exception { + throw new UnsupportedOperationException("Xalan does not support multiple files XSLT processing"); + } + /** * Process xpath and return either node or wrapped atomic value * @param data xml diff --git a/Backend/tools-services/src/main/java/com/r11/tools/xml/XmlEngine.java b/Backend/tools-services/src/main/java/com/r11/tools/xml/XmlEngine.java index 132c9d4..7e7b8db 100644 --- a/Backend/tools-services/src/main/java/com/r11/tools/xml/XmlEngine.java +++ b/Backend/tools-services/src/main/java/com/r11/tools/xml/XmlEngine.java @@ -1,8 +1,11 @@ package com.r11.tools.xml; +import com.r11.tools.model.XMLMultipleFilesData; import com.r11.tools.model.XPathQueryResult; public interface XmlEngine { + + String processXSLT(XMLMultipleFilesData[] data, String transform) throws Exception; XPathQueryResult processXPath(String data, String query, String version) throws Exception; String processXSLT(String data, String transform) throws Exception; String validate(String data, String xsd) throws Exception; diff --git a/Frontend/src/components/xml/XmlOutputFieldComponent.vue b/Frontend/src/components/xml/XmlOutputFieldComponent.vue index 116cd87..70dafb1 100644 --- a/Frontend/src/components/xml/XmlOutputFieldComponent.vue +++ b/Frontend/src/components/xml/XmlOutputFieldComponent.vue @@ -108,6 +108,7 @@ function prepareRequestBody():string { "processor": engine.value, "version": version.value }); + console.log(requestBody) return requestBody; }