Added backend support for multiple files in XSLT. (#272)

Co-authored-by: Adam Bem <adam.bem@zoho.eu>
Reviewed-on: #272
Reviewed-by: Adam Bem <bema@noreply.example.com>
Co-authored-by: widlam <mikolaj.widla@gmail.com>
Co-committed-by: widlam <mikolaj.widla@gmail.com>
This commit is contained in:
2023-12-04 06:59:05 +01:00
committed by Adam Bem
parent af140c30a4
commit 6f2d16c2b9
9 changed files with 262 additions and 3 deletions

View File

@@ -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();

View File

@@ -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));
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;