Added backend support for multiple files in XSLT. #272
| @@ -42,6 +42,7 @@ public class SparkApplication { | |||||||
|         registry.registerController(new ProcessorInfoController(logger, saxon, xalan)); |         registry.registerController(new ProcessorInfoController(logger, saxon, xalan)); | ||||||
|  |  | ||||||
|         registry.registerController(new XmlController(gson, 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.registerController(new JsonController(gson, jsongson, logger)); | ||||||
|  |  | ||||||
|         registry.register(); |         registry.register(); | ||||||
|   | |||||||
| @@ -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)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -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; | ||||||
| 
				
					
						bema
						commented  Same as above Same as above | |||||||
|  |  | ||||||
|  | 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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,11 +1,16 @@ | |||||||
| package com.r11.tools.xml; | package com.r11.tools.xml; | ||||||
|  |  | ||||||
|  | import com.r11.tools.model.XMLMultipleFilesData; | ||||||
| import com.r11.tools.model.XPathQueryResult; | import com.r11.tools.model.XPathQueryResult; | ||||||
| import net.sf.saxon.s9api.*; | import net.sf.saxon.s9api.*; | ||||||
|  |  | ||||||
| import javax.xml.transform.stream.StreamSource; | import javax.xml.transform.stream.StreamSource; | ||||||
| import java.io.StringReader; | import java.io.*; | ||||||
| import java.io.StringWriter; | 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 |  * Handler for Saxon engine | ||||||
| @@ -13,6 +18,64 @@ import java.io.StringWriter; | |||||||
|  */ |  */ | ||||||
| public class Saxon implements XmlEngine{ | 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. | ||||||
|  |      */ | ||||||
| 
				
					
						bema
						commented  I would extract some parts of the code from this method to separate private methods to increase readability I would extract some parts of the code from this method to separate private methods to increase readability | |||||||
|  |     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 |      * Transforms string containing xml document via xslt | ||||||
|      * @param data xml to be transformed |      * @param data xml to be transformed | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| package com.r11.tools.xml; | package com.r11.tools.xml; | ||||||
|  |  | ||||||
|  | import com.r11.tools.model.XMLMultipleFilesData; | ||||||
| import com.r11.tools.model.XPathQueryResult; | import com.r11.tools.model.XPathQueryResult; | ||||||
| import org.apache.xpath.XPathAPI; | import org.apache.xpath.XPathAPI; | ||||||
| import org.w3c.dom.Document; | import org.w3c.dom.Document; | ||||||
| @@ -17,7 +18,10 @@ import javax.xml.transform.stream.StreamSource; | |||||||
| import javax.xml.validation.Schema; | import javax.xml.validation.Schema; | ||||||
| import javax.xml.validation.SchemaFactory; | import javax.xml.validation.SchemaFactory; | ||||||
| import javax.xml.validation.Validator; | 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 |  * Handler for Xalan engine | ||||||
| @@ -57,6 +61,11 @@ public class Xalan implements XmlEngine{ | |||||||
|         return nodeType == Node.CDATA_SECTION_NODE || nodeType == Node.TEXT_NODE; |         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 |      * Process xpath and return either node or wrapped atomic value | ||||||
|      * @param data xml |      * @param data xml | ||||||
|   | |||||||
| @@ -1,8 +1,11 @@ | |||||||
| package com.r11.tools.xml; | package com.r11.tools.xml; | ||||||
|  |  | ||||||
|  | import com.r11.tools.model.XMLMultipleFilesData; | ||||||
| import com.r11.tools.model.XPathQueryResult; | import com.r11.tools.model.XPathQueryResult; | ||||||
|  |  | ||||||
| public interface XmlEngine { | public interface XmlEngine { | ||||||
|  |  | ||||||
|  |     String processXSLT(XMLMultipleFilesData[] data, String transform) throws Exception; | ||||||
|     XPathQueryResult processXPath(String data, String query, String version) throws Exception; |     XPathQueryResult processXPath(String data, String query, String version) throws Exception; | ||||||
|     String processXSLT(String data, String transform) throws Exception; |     String processXSLT(String data, String transform) throws Exception; | ||||||
|     String validate(String data, String xsd) throws Exception; |     String validate(String data, String xsd) throws Exception; | ||||||
|   | |||||||
| @@ -108,6 +108,7 @@ function prepareRequestBody():string { | |||||||
|         "processor": engine.value, |         "processor": engine.value, | ||||||
|         "version": version.value |         "version": version.value | ||||||
|     }); |     }); | ||||||
|  |     console.log(requestBody) | ||||||
| 
				
					
						bema
						commented  We should left console.logs like this in production code as it serves no purpose, altough I understand that can happen ;) We should left console.logs like this in production code as it serves no purpose, altough I understand that can happen ;) | |||||||
|     return requestBody; |     return requestBody; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	
Unused import, removal needed