/* * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.qetest.xslwrapper; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.Properties; import javax.xml.transform.Source; import javax.xml.transform.Templates; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import static jaxp.library.JAXPTestUtilities.filenameToURL; /** * Implementation of TransformWrapper that uses the TrAX API and uses in-memory * streams for it's sources. * *

* This implementation separates the process of reading xml and xsl files from * disk into byte arrays out from the time processing of a new * StreamSource(byte[]) takes to build a stylesheet. It also separates the time * of performing the transformation to a StreamResult(byte[]) from the time * spent simply sending the byte[] through a FileOutputStream to disk.

* *

* Important! The underlying System property of * javax.xml.transform.TransformerFactory will determine the actual TrAX * implementation used. This value will be reported out in our * getProcessorInfo() method.

* */ public class TraxStreamWrapper extends TransformWrapperHelper { /** * TransformerFactory to use; constructed in newProcessor(). */ private TransformerFactory factory; /** * Templates to use for buildStylesheet(). */ private Templates builtTemplates; /** * Cached copy of newProcessor() properties. */ private Properties newProcessorOpts; /** * Get a general description of this wrapper itself. * * @return Uses TrAX to perform transforms from StreamSource(systemId) */ public String getDescription() { return "Uses TrAX to perform transforms from StreamSource(stream)"; } /** * Get a specific description of the wrappered processor. * * @return specific description of the underlying processor or transformer * implementation: this should include both the general product name, as * well as specific version info. If possible, should be implemented without * actively creating an underlying processor. */ @Override public Properties getProcessorInfo() { Properties p = TraxWrapperUtils.getTraxInfo(); p.put("traxwrapper.method", "streams"); p.put("traxwrapper.desc", getDescription()); return p; } /** * Actually create/initialize an underlying processor or factory. * * For TrAX/javax.xml.transform implementations, this creates a new * TransformerFactory. * * @param options Properties of options, unused. * * @return (Object)getProcessor() as a side-effect, this will be null if * there was any problem creating the processor OR if the underlying * implementation doesn't use this. * @throws javax.xml.transform.TransformerConfigurationException when * actual implementation doesn't support StreamSource or StreamResult. */ @Override public TransformerFactory newProcessor(Properties options) throws TransformerConfigurationException { newProcessorOpts = options; reset(false); factory = TransformerFactory.newInstance(); // Verify the factory supports Streams! if (!(factory.getFeature(StreamSource.FEATURE) && factory.getFeature(StreamResult.FEATURE))) { throw new TransformerConfigurationException("TraxStreamWrapper.newProcessor: factory does not support Streams!"); } // Set any of our options as Attributes on the factory TraxWrapperUtils.setAttributes(factory, options); return factory; } /** * Transform supplied xmlName file with the stylesheet in the xslName file * into a resultName file. * * Names are assumed to be local path\filename references, and will be read * as byte streams before being passed to underlying StreamSources, etc. * * @param xmlName local path\filename of XML file to transform * @param xslName local path\filename of XSL stylesheet to use * @param resultName local path\filename to put result in * * @throws Exception any underlying exceptions from the wrappered processor * are simply allowed to propagate; throws a RuntimeException if any other * problems prevent us from actually completing the operation */ @Override public void transform(String xmlName, String xslName, String resultName) throws Exception { preventFootShooting(); File xslFile = new File(xslName); int xslLength = new Long(xslFile.length()).intValue(); byte[] xslBytes = new byte[xslLength]; File xmlFile = new File(xmlName); int xmlLength = new Long(xmlFile.length()).intValue(); byte[] xmlBytes = new byte[xmlLength]; try (FileInputStream xslStream = new FileInputStream(xslFile); FileInputStream xmlStream = new FileInputStream(xmlFile)) { // Read xsl into a byte array xslStream.read(xslBytes); // Create StreamSource and setSystemId StreamSource xslSource = new StreamSource(new ByteArrayInputStream(xslBytes)); // Note that systemIds must be a legal URI xslSource.setSystemId(filenameToURL(xslName)); // Build Transformer from StreamSource Transformer transformer = factory.newTransformer(xslSource); // Read xml into a byte array xmlStream.read(xmlBytes); // Create StreamSource and setSystemId StreamSource xmlSource = new StreamSource(new ByteArrayInputStream(xmlBytes)); xmlSource.setSystemId(filenameToURL(xmlName)); // Create StreamResult ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); StreamResult byteResult = new StreamResult(outBytes); // Set any of our options as Attributes on the transformer TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); // Apply any parameters needed applyParameters(transformer); // Build xml (so to speak) and transform transformer.transform(xmlSource, byteResult); // writeResults from the byte array byte[] writeBytes = outBytes.toByteArray(); // Should this be timed too or not? try (FileOutputStream writeStream = new FileOutputStream(resultName)) { writeStream.write(writeBytes); } } } /** * Pre-build/pre-compile a stylesheet. * * Although the actual mechanics are implementation-dependent, most * processors have some method of pre-setting up the data needed by the * stylesheet itself for later use in transforms. In * TrAX/javax.xml.transform, this equates to creating a Templates object. * * Sets isStylesheetReady() to true if it succeeds. Users can then call * transformWithStylesheet(xmlName, resultName) to actually perform a * transformation with this pre-built stylesheet. * * @param xslName local path\filename of XSL stylesheet to use * * @throws Exception any underlying exceptions from the wrappered processor * are simply allowed to propagate; throws a RuntimeException if any other * problems prevent us from actually completing the operation * * @see #transformWithStylesheet(String xmlName, String resultName) */ @Override public void buildStylesheet(String xslName) throws Exception { preventFootShooting(); File xslFile = new File(xslName); int xslLength = new Long(xslFile.length()).intValue(); byte[] xslBytes = new byte[xslLength]; try(FileInputStream xslStream = new FileInputStream(xslFile)) { // Read xsl into a byte array xslStream.read(xslBytes); // Create StreamSource and setSystemId StreamSource xslSource = new StreamSource(new ByteArrayInputStream(xslBytes)); // Note that systemIds must be a legal URI xslSource.setSystemId(filenameToURL(xslName)); // Build Transformer from StreamSource builtTemplates = factory.newTemplates(xslSource); // Set internal state that we have a templates ready // Note: in theory, there's no need to check builtTemplates // since the newTemplates should never return null // (it might have thrown an exception, but we don't care) m_stylesheetReady = true; } } /** * Transform supplied xmlName file with a pre-built/pre-compiled stylesheet * into a resultName file. * * User must have called buildStylesheet(xslName) beforehand, obviously. * Names are assumed to be local path\filename references, and will be * converted to URLs as needed. * * @param xmlName local path\filename of XML file to transform * @param resultName local path\filename to put result in * * @throws Exception any underlying exceptions from the wrapped processor * are simply allowed to propagate; throws a RuntimeException if any other * problems prevent us from actually completing the operation; throws an * IllegalStateException if isStylesheetReady() == false. * * @see #buildStylesheet(String xslName) */ @Override public void transformWithStylesheet(String xmlName, String resultName) throws Exception { if (!isStylesheetReady()) { throw new IllegalStateException("transformWithStylesheet() when isStylesheetReady() == false"); } preventFootShooting(); try (FileOutputStream writeStream = new FileOutputStream(resultName)) { // Get Transformer from Templates Transformer transformer = builtTemplates.newTransformer(); File xmlFile = new File(xmlName); int xmlLength = new Long(xmlFile.length()).intValue(); byte[] xmlBytes = new byte[xmlLength]; FileInputStream xmlStream = new FileInputStream(xmlFile); // Read xml into a byte array xmlStream.read(xmlBytes); // Create StreamSource and setSystemId StreamSource xmlSource = new StreamSource(new ByteArrayInputStream(xmlBytes)); xmlSource.setSystemId(filenameToURL(xmlName)); // Create StreamResult ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); StreamResult byteResult = new StreamResult(outBytes); // Set any of our options as Attributes on the transformer TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); // Untimed: Apply any parameters needed applyParameters(transformer); // Build xml (so to speak) and transform transformer.transform(xmlSource, byteResult); // writeResults from the byte array byte[] writeBytes = outBytes.toByteArray(); writeStream.write(writeBytes); } } /** * Transform supplied xmlName file with a stylesheet found in an * xml-stylesheet PI into a resultName file. * * Names are assumed to be local path\filename references, and will be * converted to URLs as needed. Implementations will use whatever facilities * exist in their wrappered processor to fetch and build the stylesheet to * use for the transform. * * @param xmlName local path\filename of XML file to transform * @param resultName local path\filename to put result in * * @throws Exception any underlying exceptions from the wrapped processor * are simply allowed to propagate; throws a RuntimeException if any other * problems prevent us from actually completing the operation */ @Override public void transformEmbedded(String xmlName, String resultName) throws Exception { preventFootShooting(); File xmlFile = new File(xmlName); try (FileOutputStream writeStream = new FileOutputStream(resultName);) { int xmlLength = new Long(xmlFile.length()).intValue(); byte[] xmlBytes = new byte[xmlLength]; FileInputStream xmlStream = new FileInputStream(xmlFile); // Read xml into a byte array xmlStream.read(xmlBytes); // Create StreamSource and setSystemId StreamSource xmlSource = new StreamSource(new ByteArrayInputStream(xmlBytes)); xmlSource.setSystemId(filenameToURL(xmlName)); // Read xsl from the xml document Source xslSource = factory.getAssociatedStylesheet(xmlSource, null, null, null); // Build xsl from a URL Transformer transformer = factory.newTransformer(xslSource); // Re-read the XML file for use in transform; not timed xmlFile = new File(xmlName); xmlLength = new Long(xmlFile.length()).intValue(); xmlBytes = new byte[xmlLength]; xmlStream = new FileInputStream(xmlFile); xmlStream.read(xmlBytes); // Create StreamSource and setSystemId xmlSource = new StreamSource(new ByteArrayInputStream(xmlBytes)); xmlSource.setSystemId(filenameToURL(xmlName)); // Create StreamResult ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); StreamResult byteResult = new StreamResult(outBytes); // Set any of our options as Attributes on the transformer TraxWrapperUtils.setAttributes(transformer, newProcessorOpts); // Apply any parameters needed applyParameters(transformer); // Build xml (so to speak) and transform transformer.transform(xmlSource, byteResult); // writeResults from the byte array byte[] writeBytes = outBytes.toByteArray(); writeStream.write(writeBytes); } } /** * Reset our parameters and wrapper state, and optionally force creation of * a new underlying processor implementation. * * This always clears our built stylesheet and any parameters that have been * set. If newProcessor is true, also forces a re-creation of our underlying * processor as if by calling newProcessor(). * * @param newProcessor if we should reset our underlying processor * implementation as well */ @Override public void reset(boolean newProcessor) { super.reset(newProcessor); // clears indent and parameters m_stylesheetReady = false; builtTemplates = null; if (newProcessor) { try { newProcessor(newProcessorOpts); } catch (TransformerConfigurationException ignore) {} } } /** * Apply a single parameter to a Transformer. * * Overridden to take a Transformer and call setParameter(). * * @param transformer a Transformer object. * @param namespace for the parameter, may be null * @param name for the parameter, should not be null * @param value for the parameter, may be null */ @Override protected void applyParameter(Transformer transformer, String namespace, String name, String value) { try { // Munge the namespace into the name per // javax.xml.transform.Transformer.setParameter() if (null != namespace) { name = "{" + namespace + "}" + name; } transformer.setParameter(name, value); } catch (Exception e) { throw new IllegalArgumentException("applyParameter threw: " + e.toString()); } } /** * Ensure newProcessor has been called when needed. * * Prevent users from shooting themselves in the foot by calling a * transform* API before newProcessor(). * * @throws java.lang.Exception */ public void preventFootShooting() throws Exception { if (null == factory) { newProcessor(newProcessorOpts); } } }