blob: 8d06cc1d93bc70f7f48d5feabfe6e80d66d649bd [file] [log] [blame]
/*
* 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.
*/
/*
* $Id$
*/
package org.apache.qetest.xslwrapper;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.util.Hashtable;
import java.util.Properties;
import javax.xml.parsers.SAXParser;
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 org.apache.qetest.QetestUtils;
import org.apache.xml.utils.DefaultErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
/**
* Implementation of TransformWrapper that uses the TrAX API and
* uses SAXSource/SAXResult whenever possible.
*
* <p>This implementation uses SAX to build the stylesheet and
* to perform the transformation.</p>
*
* <p><b>Important!</b> 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.</p>
*
* @author Shane Curcuru
* @version $Id$
*/
public class TraxSAXWrapper extends TransformWrapperHelper
{
/**
* TransformerFactory to use; constructed in newProcessor().
*/
protected TransformerFactory factory = null;
/**
* SAXTransformerFactory we actually use; constructed in newProcessor().
*/
protected SAXTransformerFactory saxFactory = null;
/**
* Templates to use for buildStylesheet().
*/
protected Templates builtTemplates = null;
/**
* Cached copy of newProcessor() Hashtable.
*/
protected Hashtable 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.
*/
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 Exception covers any underlying exceptions thrown
* by the actual implementation
*/
public Object newProcessor(Hashtable options) throws Exception
{
newProcessorOpts = options;
//@todo do we need to do any other cleanup?
reset(false);
factory = TransformerFactory.newInstance();
factory.setErrorListener(new DefaultErrorHandler());
// 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 (Object)saxFactory;
}
/**
* Transform supplied xmlName file with the stylesheet in the
* xslName file into a resultName file using SAX.
*
* Pseudocode:
* <code>
* // 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
* </code>
*
* @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
*
* @return array of longs denoting timing of all parts of
* our operation: IDX_OVERALL, IDX_XSLBUILD,
* IDX_TRANSFORM, IDX_RESULTWRITE
*
* @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
*/
public long[] transform(String xmlName, String xslName, String resultName)
throws Exception
{
preventFootShooting();
long startTime = 0;
long xslBuild = 0;
long transform = 0;
long resultWrite = 0;
// 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);
// Timed: read/build Templates from StreamSource
startTime = System.currentTimeMillis();
xslReader.parse(QetestUtils.filenameToURL(xslName));
xslBuild = System.currentTimeMillis() - startTime;
// 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(QetestUtils.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);
// Timed: Parse the XML input document and do transform
startTime = System.currentTimeMillis();
xmlReader.parse(QetestUtils.filenameToURL(xmlName));
transform = System.currentTimeMillis() - startTime;
// Timed: writeResults from the byte array
startTime = System.currentTimeMillis();
byte[] writeBytes = outBytes.toByteArray(); // Should this be timed too or not?
// @see TraxStreamWrapper
FileOutputStream writeStream = new FileOutputStream(resultName);
writeStream.write(writeBytes);
writeStream.close();
resultWrite = System.currentTimeMillis() - startTime;
long[] times = getTimeArray();
times[IDX_OVERALL] = xslBuild + transform + resultWrite;
times[IDX_XSLBUILD] = xslBuild;
times[IDX_TRANSFORM] = transform;
times[IDX_RESULTWRITE] = resultWrite;
return times;
}
/**
* 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
*
* @return array of longs denoting timing of all parts of
* our operation: IDX_OVERALL, IDX_XSLBUILD
*
* @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)
*/
public long[] buildStylesheet(String xslName) throws Exception
{
preventFootShooting();
long startTime = 0;
long xslBuild = 0;
// 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);
// Timed: read/build Templates from StreamSource
startTime = System.currentTimeMillis();
xslReader.parse(QetestUtils.filenameToURL(xslName));
xslBuild = System.currentTimeMillis() - startTime;
// Also set systemId to the stylesheet
templatesHandler.setSystemId(QetestUtils.filenameToURL(xslName));
// Get the Templates object from the ContentHandler.
builtTemplates = templatesHandler.getTemplates();
m_stylesheetReady = true;
long[] times = getTimeArray();
times[IDX_OVERALL] = xslBuild;
times[IDX_XSLBUILD] = xslBuild;
return times;
}
/**
* 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
*
* @return array of longs denoting timing of all parts of
* our operation: IDX_OVERALL,
* IDX_XMLREAD, IDX_TRANSFORM, IDX_RESULTWRITE
*
* @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)
*/
public long[] transformWithStylesheet(String xmlName, String resultName)
throws Exception
{
if (!isStylesheetReady())
throw new IllegalStateException("transformWithStylesheet() when isStylesheetReady() == false");
preventFootShooting();
long startTime = 0;
long xslRead = 0;
long xslBuild = 0;
long xmlRead = 0;
long transform = 0;
long resultWrite = 0;
// 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
startTime = System.currentTimeMillis();
xmlReader.parse(QetestUtils.filenameToURL(xmlName));
transform = System.currentTimeMillis() - startTime;
// Timed: writeResults from the byte array
startTime = System.currentTimeMillis();
byte[] writeBytes = outBytes.toByteArray(); // Should this be timed too or not?
// @see TraxStreamWrapper
FileOutputStream writeStream = new FileOutputStream(resultName);
writeStream.write(writeBytes);
writeStream.close();
resultWrite = System.currentTimeMillis() - startTime;
long[] times = getTimeArray();
times[IDX_OVERALL] = transform + resultWrite;
times[IDX_TRANSFORM] = transform;
times[IDX_RESULTWRITE] = resultWrite;
return times;
}
/**
* 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
*
* @return array of longs denoting timing of only these parts of
* our operation: IDX_OVERALL, IDX_XSLREAD (time to find XSL
* reference from the xml-stylesheet PI), IDX_XSLBUILD, (time
* to then build the Transformer therefrom), IDX_TRANSFORM
*
* @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
*/
public long[] transformEmbedded(String xmlName, String resultName)
throws Exception
{
preventFootShooting();
long startTime = 0;
long xslRead = 0;
long xslBuild = 0;
long transform = 0;
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
*/
public void reset(boolean newProcessor)
{
super.reset(newProcessor); // clears indent and parameters
m_stylesheetReady = false;
builtTemplates = null;
if (newProcessor)
{
try
{
newProcessor(newProcessorOpts);
}
catch (Exception e)
{
//@todo Hmm: what should we do here?
}
}
}
/**
* Apply a single parameter to a Transformer.
*
* Overridden to take a Transformer and call setParameter().
*
* @param passThru to be passed to each applyParameter() method
* call - for TrAX, you might pass 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
*/
protected void applyParameter(Object passThru, String namespace,
String name, Object value)
{
try
{
Transformer t = (Transformer)passThru;
// Munge the namespace into the name per
// javax.xml.transform.Transformer.setParameter()
if (null != namespace)
{
name = "{" + namespace + "}" + name;
}
t.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)
*/
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)
*/
protected XMLReader getJAXPXMLReader()
throws Exception
{
// Be sure to use the JAXP methods only!
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
SAXParser saxParser = factory.newSAXParser();
return saxParser.getXMLReader();
}
}