--- /dev/null 2014-09-08 10:45:56.830930409 -0700 +++ new/test/javax/xml/jaxp/libs/org/apache/qetest/xslwrapper/TraxStreamWrapper.java 2015-01-09 15:42:29.901200005 -0800 @@ -0,0 +1,409 @@ +/* + * Copyright (c) 2015, 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 { + /** + * Templates to use for buildStylesheet(). + */ + private Templates builtTemplates; + + /** + * 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()); + } + } +}