--- /dev/null 2014-09-08 10:45:56.830930409 -0700 +++ new/test/javax/xml/jaxp/libs/org/apache/qetest/xslwrapper/TraxSAXWrapper.java 2014-12-31 11:41:20.828149788 -0800 @@ -0,0 +1,489 @@ +/* + * 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.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.util.Properties; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TemplatesHandler; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamResult; +import static jaxp.library.JAXPTestUtilities.filenameToURL; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +/** + * Implementation of TransformWrapper that uses the TrAX API and uses + * SAXSource/SAXResult whenever possible. + * This implementation uses SAX to build the stylesheet and to perform the + * transformation. + *

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 TraxSAXWrapper extends TransformWrapperHelper { + + /** + * TransformerFactory to use; constructed in newProcessor(). + */ + private TransformerFactory factory; + + /** + * SAXTransformerFactory we actually use; constructed in newProcessor(). + */ + private SAXTransformerFactory saxFactory; + + /** + * Templates to use for buildStylesheet(). + */ + private Templates builtTemplates; + + /** + * Cached copy of newProcessor() properties. + */ + protected Properties newProcessorOpts = null; + + /** + * Get a general description of this wrapper itself. + * + * @return Uses TrAX to perform transforms from SAXSource(systemId) + */ + public String getDescription() { + return "Uses TrAX to perform transforms from SAXSource(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", "sax"); + 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 Hashtable 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 SAX! + if (!(factory.getFeature(SAXSource.FEATURE) + && factory.getFeature(SAXResult.FEATURE))) { + throw new TransformerConfigurationException("TraxSAXWrapper.newProcessor: factory does not support SAX!"); + } + // Set any of our options as Attributes on the factory + TraxWrapperUtils.setAttributes(factory, options); + saxFactory = (SAXTransformerFactory) factory; + return saxFactory; + } + + /** + * Transform supplied xmlName file with the stylesheet in the xslName file + * into a resultName file using SAX. + * + * Pseudocode: + * // Read/build stylesheet + * xslReader.setContentHandler(templatesHandler); + * xslReader.parse(xslName); + * + * xslOutputProps = templates.getOutputProperties(); + * // Set features and tie in DTD, lexical, etc. handling + * + * serializingHandler.getTransformer().setOutputProperties(xslOutputProps); + * serializingHandler.setResult(new StreamResult(outBytes)); + * stylesheetHandler.setResult(new SAXResult(serializingHandler)); + * xmlReader.setContentHandler(stylesheetHandler); + * // Perform Transform + * xmlReader.parse(xmlName); + * // Separately: write bytes to disk + * + * + * @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(); + + // Create a ContentHandler to handle parsing of the xsl + TemplatesHandler templatesHandler = saxFactory.newTemplatesHandler(); + + // Create an XMLReader and set its ContentHandler. + // Be sure to use the JAXP methods only! + XMLReader xslReader = getJAXPXMLReader(); + xslReader.setContentHandler(templatesHandler); + + // read/build Templates from StreamSource + xslReader.parse(filenameToURL(xslName)); + + // Get the Templates object from the ContentHandler. + Templates templates = templatesHandler.getTemplates(); + // Get the outputProperties from the stylesheet, so + // we can later use them for the serialization + Properties xslOutputProps = templates.getOutputProperties(); + + // Create a ContentHandler to handle parsing of the XML + TransformerHandler stylesheetHandler = saxFactory.newTransformerHandler(templates); + // Also set systemId to the stylesheet + stylesheetHandler.setSystemId(filenameToURL(xslName)); + + // Untimed: Set any of our options as Attributes on the transformer + TraxWrapperUtils.setAttributes(stylesheetHandler.getTransformer(), newProcessorOpts); + + // Apply any parameters needed + applyParameters(stylesheetHandler.getTransformer()); + + // Use a new XMLReader to parse the XML document + XMLReader xmlReader = getJAXPXMLReader(); + xmlReader.setContentHandler(stylesheetHandler); + + // Set the ContentHandler to also function as LexicalHandler, + // includes "lexical" events (e.g., comments and CDATA). + xmlReader.setProperty( + "http://xml.org/sax/properties/lexical-handler", + stylesheetHandler); + + // Also attempt to set as a DeclHandler, which Xalan-J + // supports even though it is not required by JAXP + // Ignore exceptions for other processors since this + // is not a required setting + try { + xmlReader.setProperty( + "http://xml.org/sax/properties/declaration-handler", + stylesheetHandler); + } catch (SAXException se) { /* no-op - ignore */ } + + // added by sb. Tie together DTD and other handling + xmlReader.setDTDHandler(stylesheetHandler); + try { + xmlReader.setFeature( + "http://xml.org/sax/features/namespace-prefixes", + true); + } catch (SAXException se) { /* no-op - ignore */ } + try { + xmlReader.setFeature( + "http://apache.org/xml/features/validation/dynamic", + true); + } catch (SAXException se) { /* no-op - ignore */ } + + // Create a 'pipe'-like identity transformer, so we can + // easily get our results serialized to disk + TransformerHandler serializingHandler = saxFactory.newTransformerHandler(); + // Set the stylesheet's output properties into the output + // via it's Transformer + serializingHandler.getTransformer().setOutputProperties(xslOutputProps); + + // Create StreamResult to byte stream in memory + ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); + StreamResult byteResult = new StreamResult(outBytes); + serializingHandler.setResult(byteResult); + + // Create a SAXResult dumping into our 'pipe' serializer + // and tie in lexical handling (is any other handling needed?) + SAXResult saxResult = new SAXResult(serializingHandler); + saxResult.setLexicalHandler(serializingHandler); + + // Set the original stylesheet to dump into our result + stylesheetHandler.setResult(saxResult); + + // Parse the XML input document and do transform + xmlReader.parse(filenameToURL(xmlName)); + + // 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 wrapped 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(); + + // Create a ContentHandler to handle parsing of the xsl + TemplatesHandler templatesHandler = saxFactory.newTemplatesHandler(); + + // Create an XMLReader and set its ContentHandler. + XMLReader xslReader = getJAXPXMLReader(); + xslReader.setContentHandler(templatesHandler); + + // read/build Templates from StreamSource + xslReader.parse(filenameToURL(xslName)); + + // Also set systemId to the stylesheet + templatesHandler.setSystemId(filenameToURL(xslName)); + + // Get the Templates object from the ContentHandler. + builtTemplates = templatesHandler.getTemplates(); + 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 wrappered 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(); + + // Get the outputProperties from the stylesheet, so + // we can later use them for the serialization + Properties xslOutputProps = builtTemplates.getOutputProperties(); + + // Create a ContentHandler to handle parsing of the XML + TransformerHandler stylesheetHandler = saxFactory.newTransformerHandler(builtTemplates); + + // Untimed: Set any of our options as Attributes on the transformer + TraxWrapperUtils.setAttributes(stylesheetHandler.getTransformer(), newProcessorOpts); + + // Apply any parameters needed + applyParameters(stylesheetHandler.getTransformer()); + + // Use a new XMLReader to parse the XML document + XMLReader xmlReader = getJAXPXMLReader(); + xmlReader.setContentHandler(stylesheetHandler); + + // Set the ContentHandler to also function as LexicalHandler, + // includes "lexical" events (e.g., comments and CDATA). + xmlReader.setProperty( + "http://xml.org/sax/properties/lexical-handler", + stylesheetHandler); + xmlReader.setProperty( + "http://xml.org/sax/properties/declaration-handler", + stylesheetHandler); + + // added by sb. Tie together DTD and other handling + xmlReader.setDTDHandler(stylesheetHandler); + try { + xmlReader.setFeature( + "http://xml.org/sax/features/namespace-prefixes", + true); + } catch (SAXException se) { /* no-op - ignore */ } + try { + xmlReader.setFeature( + "http://apache.org/xml/features/validation/dynamic", + true); + } catch (SAXException se) { /* no-op - ignore */ } + + // Create a 'pipe'-like identity transformer, so we can + // easily get our results serialized to disk + TransformerHandler serializingHandler = saxFactory.newTransformerHandler(); + // Set the stylesheet's output properties into the output + // via it's Transformer + serializingHandler.getTransformer().setOutputProperties(xslOutputProps); + + // Create StreamResult to byte stream in memory + ByteArrayOutputStream outBytes = new ByteArrayOutputStream(); + StreamResult byteResult = new StreamResult(outBytes); + serializingHandler.setResult(byteResult); + + // Create a SAXResult dumping into our 'pipe' serializer + // and tie in lexical handling (is any other handling needed?) + SAXResult saxResult = new SAXResult(serializingHandler); + saxResult.setLexicalHandler(serializingHandler); + + // Set the original stylesheet to dump into our result + stylesheetHandler.setResult(saxResult); + + // Timed: Parse the XML input document and do transform + xmlReader.parse(filenameToURL(xmlName)); + + // Timed: 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); + } + } + + /** + * 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 wrappered 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 { + throw new RuntimeException("TraxSAXWrapper.transformEmbedded not implemented yet!"); + } + + /** + * 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(). + * + * (Sorry, I couldn't resist) + * @throws java.lang.Exception + */ + public void preventFootShooting() throws Exception { + if (null == factory) { + newProcessor(newProcessorOpts); + } + } + + /** + * Worker method to get an XMLReader. + * + * Not the most efficient of methods, but makes the code simpler. + * + * @return a new XMLReader for use, with setNamespaceAware(true) + * @throws java.lang.Exception + */ + protected XMLReader getJAXPXMLReader() throws Exception { + // Be sure to use the JAXP methods only! + SAXParserFactory sfactory = SAXParserFactory.newInstance(); + sfactory.setNamespaceAware(true); + return sfactory.newSAXParser().getXMLReader(); + } +}