Skip to Content.
Sympa Menu

xom-interest - [XOM-interest] XSLTransform constructor specifying TransformerFactory

xom-interest AT lists.ibiblio.org

Subject: XOM API for Processing XML with Java

List archive

Chronological Thread  
  • From: Wolfgang Hoschek <whoschek AT lbl.gov>
  • To: xom-interest AT lists.ibiblio.org
  • Subject: [XOM-interest] XSLTransform constructor specifying TransformerFactory
  • Date: Thu, 10 Feb 2005 09:54:26 -0800

While you are looking at the XSLTransform, have a look at the patch I've been carrying for more than half a year now. It adds an extra constructor with a TransformerFactory argument. This is needed for thread-safety and flexibility, and is backwards compatible.

Remember that configuring the desired XSLT impl. via a System property is not thread safe; a system property is effectively a global variable, and can cause race condition havoc in a setting where multiple idependent applications or application modules in the same VM try to use different XSLT implementations.

Also not that a TransformerFactory is also not thread-safe, hence the safe and efficient work-arounds in my own factory as additional background documentation (also attached below).

In summary, the proposal is to add an additional constructor with an additional argument.
Any comments?

Wolfgang.


// Copyright 2002-2004 Elliotte Rusty Harold
//
// This library is free software; you can redistribute
// it and/or modify it under the terms of version 2.1 of
// the GNU Lesser General Public License as published by
// the Free Software Foundation.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General
// Public License along with this library; if not, write to the
// Free Software Foundation, Inc., 59 Temple Place, Suite 330,
// Boston, MA 02111-1307 USA
//
// You can contact Elliotte Rusty Harold by sending e-mail to
// elharo AT metalab.unc.edu. Please include the word "XOM" in the
// subject line. The XOM home page is temporarily located at
// http://www.cafeconleche.org/XOM/ but will eventually move
// to http://www.xom.nu/

package nu.xom.xslt;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.TransformerFactory;

import org.xml.sax.SAXParseException;

import nu.xom.Document;
import nu.xom.Element;
import nu.xom.NodeFactory;
import nu.xom.Nodes;
import nu.xom.XMLException;

/**
* <p>
* This patch adds a TransformerFactory constructor argument and requires
building XOM from source with sealed="false" flag in XOM jar target;
* This is needed for thread-safety and flexibility, and is backwards
compatible.
* <p>
* Hopefully this (or an equivalent) will be included in a future XOM version;
* Serves as an interface to a TrAX aware XSLT processor such as Xalan
* or Saxon. The following example shows how to apply an XSL
* Transformation to a XOM document and get the transformation result
* in the form of a XOM <code>Nodes</code>:</p>
* <blockquote><pre>public static Nodes transform(Document in)
* throws XSLException, ParsingException, IOException {
* Builder builder = new Builder();
* Document stylesheet = builder.build("mystylesheet.xsl");
* XSLTransform stylesheet = new XSLTransform(stylesheet);
* return stylesheet.transform(doc);
* } </pre></blockquote>
*
* <p>
* XOM relies on TrAX to perform the transformation.
* The <code>javax.xml.transform.TransformerFactory</code> Java
* system property determines which XSLT engine TrAX uses. Its
* value should be the fully qualified name of the implementation
* of the abstract <code>javax.xml.transform.TransformerFactory</code>
* class. Values of this property for popular XSLT processors include:
* </p>
* <ul>
* <li>Saxon 6.x:
* <code>com.icl.saxon.TransformerFactoryImpl</code>
* </li>
* <li>Saxon 7.x and 8.x:
* <code>net.sf.saxon.TransformerFactoryImpl</code>
* </li>
* <li>Xalan interpretive:
* <code>org.apache.xalan.processor.TransformerFactoryImpl</code>
* </li>
* <li>Xalan XSLTC:
* <code>org.apache.xalan.xsltc.trax.TransformerFactoryImpl</code>
* </li>
* <li>jd.xslt:
* <code>jd.xml.xslt.trax.TransformerFactoryImpl</code>
* </li>
* <li>Oracle:
* <code>oracle.xml.jaxp.JXSAXTransformerFactory</code>
* </li>
* <li>Java 1.5 bundled Xalan:
*
<code>com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl</code>
* </li>
* </ul>
* <p>
* This property can be set in all the usual ways a Java system
* property can be set. TrAX picks from them in this order:</p>
* <ol>
* <li>The most recent value specified by invoking
* <code>System.setProperty("javax.xml.transform.TransformerFactory",
* "<i><code>classname</code></i>")</code></li>
* <li>The value specified at the command line using the
* <samp>-Djavax.xml.transform.TransformerFactory=<i>classname</i></samp>
* option to the <b>java</b> interpreter</li>
* <li>The class named in the <code>lib/jaxp.properties</code>
* properties file in the JRE directory, in a line like this one:
* <pre>javax.xml.parsers.DocumentBuilderFactory=<i>classname</i></pre>
* </li>
* <li>The class named in the
* <code>META-INF/services/javax.xml.transform.TransformerFactory</code>
* file in the JAR archives available to the runtime</li>
* <li>Finally, if all of the above options fail,
* a default implementation is chosen. In Sun's JDK 1.4.0 and 1.4.1,
* this is Xalan 2.2d10. In JDK 1.4.2, this is Xalan 2.4.
* In JDK 1.4.2_02, this is Xalan 2.4.1.
* In JDK 1.4.2_03, 1.5 beta 2, and 1.5 RC1 this is Xalan 2.5.2.
* In JDK 1.4.2_05, this is Xalan 2.4.1. (Yes, Sun appears to have
* reverted to 2.4.1 in 1.4.2_05.)
* </li>
* </ol>
*
* @author Elliotte Rusty Harold
* @version 1.0b4
*/
public final class XSLTransform {


/**
* <p>
* The compiled form of the XSLT stylesheet that this object
* represents. This can be safely used across multiple threads
* unlike a <code>Transformer</code> object.
* </p>
*/
private Templates templates;
private NodeFactory factory; // = new NodeFactory();


// I could use one TransformerFactory field instead of local
// variables but then I'd have to synchronize it; and it would
// be hard to change the class used to transform



/**
* <p>
* Creates a new <code>XSLTransform</code> by
* reading the stylesheet from the specified document.
* </p>
*
* @param stylesheet document containing the stylesheet
*
* @throws XSLException when the supplied document
* is not syntactically correct XSLT
*/
public XSLTransform(Document stylesheet) throws XSLException {
this(stylesheet, null);
}

/**
* <p>
* Creates a new <code>XSLTransform</code> by
* reading the stylesheet from the specified document.
* </p>
*
* @param stylesheet document containing the stylesheet
*
* @throws XSLException when the supplied document
* is not syntactically correct XSLT
*/
public XSLTransform(Document stylesheet, NodeFactory factory) throws
XSLException {
this(stylesheet, factory, null);
}


/**
* <p>
* Creates a new <code>XSLTransform</code> from the specified
<code>TranformerFactory</code>,
* reading the stylesheet from the specified document.
* </p>
*
* @param stylesheet document containing the stylesheet
*
* @throws XSLException when the supplied document
* is not syntactically correct XSLT
*/
public XSLTransform(Document stylesheet, NodeFactory factory,
TransformerFactory transformerFactory) throws XSLException {
if (factory == null) factory = new NodeFactory();
this.factory = factory;
if (transformerFactory == null) {
try {
transformerFactory =
TransformerFactory.newInstance();
} catch (TransformerFactoryConfigurationError error) {
throw new XSLException(
"Could not locate a TrAX
TransformerFactory", error);
}
}

try {
this.templates = transformerFactory.newTemplates(new
XOMSource(stylesheet));
} catch (TransformerConfigurationException ex) {
throw new XSLException("Syntax error in stylesheet",
ex);
}
}

/**
* <p>
* Creates a new <code>Nodes</code> from the
* input <code>Document</code> by applying this object's
* stylesheet. The original <code>Document</code> is not
* changed.
* </p>
*
* @param in document to transform
*
* @return a <code>Nodes</code> containing the result of the
* transformation
*
* @throws XSLException if the transformation fails, normally
* due to an XSLT error
*/
public Nodes transform(Document in) throws XSLException {
return transform(new XOMSource(in));
}

/**
* <p>
* Creates a new <code>Nodes</code> object from the
* input <code>Nodes</code> object by applying this object's
* stylesheet. The original <code>Nodes</code> object is not
* changed.
* </p>
*
* @param in document to transform
*
* @return a <code>Nodes</code> containing the result of
* the transformation
*
* @throws XSLException if the transformation fails, normally
* due to an XSLT error
*/
public Nodes transform(Nodes in) throws XSLException {
if (in.size() == 0) return new Nodes();
XOMSource source = new XOMSource(in);
return transform(source);
}


// we know our factory impl is stateless, so there is no need to create
// new factories time and again
//private static final NodeFactory FACTORY = new NodeFactory();

/**
* Returns a new nodefactory receiving the results of a transform.
* This enabling subclasses to create custom Nodes and Elements as
desired,
* for example for HTML elements.
* <p>
* Override this method for custom node factory behaviour.
* This default implementation returns a normal {@link NodeFactory}
instance.
* <p>
* Note on thread-safety: If the nodefactory is stateless or
immutable it is
* sufficient to return the same identical factory many times. If, on
the
* other hand, the node factory is stateful or mutable, subclasses
MUST
* return a new (independent) factory object each time this method is
called
* in order to ensure thread-safety.
*
* @return the node factory
*/
protected NodeFactory createNodeFactory() {
return factory;
}

/**
* <p>
* Creates a new <code>Nodes</code> object from the
* input <code>Source</code> object by applying this object's
* stylesheet.
* </p>
*
* @param in TrAX <code>Source</code> to transform
*
* @return a <code>Nodes</code> object containing the result of
* the transformation
*
* @throws XSLException if the transformation fails, normally
* due to an XSLT error
*/
private Nodes transform(Source in) throws XSLException {
// get a factory as desired by class/subclass
NodeFactory factory = createNodeFactory();
//if (factory == null) factory = new NodeFactory();

try {
XOMResult out = new XOMResult(factory);
Transformer transformer = templates.newTransformer();
// work around Xalan bug
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.transform(in, out);
return out.getResult();
}
catch (Exception ex) {
// workaround bugs that wrap RuntimeExceptions
Throwable cause = ex;
if (cause instanceof TransformerException) {
TransformerException tex = (TransformerException) cause;
Throwable nested = tex.getException();
if (nested != null) {
cause = nested;
if (cause instanceof SAXParseException) {
nested = ((SAXParseException) cause).getException();
if (nested != null) cause = nested;
}
}
}
throw new XSLException(ex.getMessage(), cause);
}

}


/**
* <p>
* Builds a <code>Document</code> object from a
* <code>Nodes</code> object. This is useful when the stylesheet
* is known to produce a well-formed document with a single root
* element and no text or attribute nodes. If the stylesheet
* produces anything else, an <code>XMLException</code> is thrown.
* </p>
*
* @param nodes the nodes to be placed in the new document
*
* @return a document containing the nodes
*
* @throws XMLException if <code>nodes</code> does not contain
* exactly one element or if it contains any text nodes or
* attributes
*/
public static Document toDocument(Nodes nodes) {

Element root = null;
int rootPosition = 0;
for (int i = 0; i < nodes.size(); i++) {
if (nodes.get(i) instanceof Element) {
rootPosition = i;
root = (Element) nodes.get(i);
break;
}
}

if (root == null) {
throw new XMLException("No root element");
}

Document result = new Document(root);

for (int i = 0; i < rootPosition; i++) {
result.insertChild(nodes.get(i), i);
}

for (int i = rootPosition+1; i < nodes.size(); i++) {
result.appendChild(nodes.get(i));
}

return result;

}


/**
* <p>
* Returns a string form of this <code>XSLTransform</code>,
* suitable for debugging.
* </p>
*
* @return debugging string
*/
public String toString() {
return "[" + getClass().getName() + ": " + templates + "]";
}


}/*
* Copyright (c) 2003, The Regents of the University of California, through
* Lawrence Berkeley National Laboratory (subject to receipt of any required
* approvals from the U.S. Dept. of Energy). All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* (1) Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* (2) Redistributions in binary form must reproduce the above copyright
notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* (3) Neither the name of the University of California, Lawrence Berkeley
* National Laboratory, U.S. Dept. of Energy nor the names of its contributors
* may be used to endorse or promote products derived from this software
without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* You are under no obligation whatsoever to provide any bug fixes, patches,
or
* upgrades to the features, functionality or performance of the source code
* ("Enhancements") to anyone; however, if you choose to make your
Enhancements
* available either publicly, or directly to Lawrence Berkeley National
* Laboratory, without imposing a separate written license agreement for such
* Enhancements, then you hereby grant the following license: a non-exclusive,
* royalty-free perpetual license to install, use, modify, prepare derivative
* works, incorporate into other computer software, distribute, and sublicense
* such enhancements or derivative works thereof, in binary and source code
* form.
*/
package nux.xom.pool;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;

import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;

import nu.xom.Document;
import nu.xom.NodeFactory;
import nu.xom.ParsingException;
import nu.xom.ValidityException;
import nu.xom.XMLException;
import nu.xom.xslt.XSLException;
import nu.xom.xslt.XSLTransform;

/**
* Creates and returns new <code>XSLTransform</code> objects using flexible
parametrization (thread-safe).
* TRAX factories, attributes and {@link URIResolver} of the underlying TRAX
* {@link TransformerFactory} can be specified by overriding the protected
* <code>getPreferredTransformerFactories</code> and <code>initFactory</code>
methods.
* <p>
* For anything but simple/basic use cases, this API is more robust,
* configurable and convenient than the underlying XOM XSLTransform
constructor API.
* <p>
* This implementation is thread-safe.
* <p>
* Example usage:
* <pre>
* // without custom factories:
* XSLTransform trans = new XSLTransformFactory().createTransform(new
File("/tmp/test.xsl"));
* Document doc = BuilderPool.GLOBAL_POOL.getBuilder(false).build(new
File("/tmp/test.xml"));
* Nodes nodes = trans.transform(doc);
* for (int i=0; i < nodes.size(); i++) {
* System.out.println("node "+i+": "+nodes.get(i).toXML());
* }
*
* // with custom factories:
* XSLTransformFactory factory = new XSLTransformFactory() {
* protected String[] getPreferredTransformerFactories() {
* return new String[] {
* "net.sf.saxon.TransformerFactoryImpl",
* "org.apache.xalan.processor.TransformerFactoryImpl"
* };
* }
* };
* XSLTransform trans = factory.createTransform(new File("/tmp/test.xsl"));
* Document doc = BuilderPool.GLOBAL_POOL.getBuilder(false).build(new
File("/tmp/test.xml"));
* Nodes nodes = trans.transform(doc);
* </pre>
*
* @author whoschek.AT.lbl.DOT.gov
* @author $Author: hoschek3 $
* @version $Revision: 1.6 $, $Date: 2005/01/13 08:56:19 $
*/
public class XSLTransformFactory {

private static final String[] DEFAULT_TRAX_FACTORIES = {
"org.apache.xalan.processor.TransformerFactoryImpl", // slow
but mostly correct

"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", //
fast and mostly correct
"net.sf.saxon.TransformerFactoryImpl", // can do XSLT 2.0 and
XQuery 1.0
"org.apache.xalan.xsltc.trax.TransformerFactoryImpl", // fast
but buggy
"com.icl.saxon.TransformerFactoryImpl",
"oracle.xml.jaxp.JXSAXTransformerFactory",
"jd.xml.xslt.trax.TransformerFactoryImpl",
};

// A TRAX TransformerFactory is not thread-safe, so we use a
ThreadLocal to make it thread-safe
private final ThreadLocal threadLocal;

private static final boolean DEBUG;

static {
boolean debug = false;
try {
debug =
"true".equals(System.getProperty("nux.xom.pool.XSLTransformFactory.debug",
"false"));
} catch (Throwable e) { // better safe than sorry
; // we can live with that
}
DEBUG = debug;
}

/**
* Creates a factory instance.
*/
public XSLTransformFactory() {
this.threadLocal = new ThreadLocal() {
protected Object initialValue() { // lazy init
// find a TRAX factory that works
final String[] traxFactories =
getPreferredTransformerFactories();
for (int i = 0; traxFactories != null && i <
traxFactories.length; i++) {
// if (DEBUG) System.err.println("trying
TRAX=" + traxFactories[i]);
try {
TransformerFactory factory =
(TransformerFactory) ClassLoaderUtil.newInstance(traxFactories[i]);
initFactory(factory);
if (DEBUG)
System.err.println("using TRAX TransformerFactory=" +
factory.getClass().getName());
return factory;
} catch (ClassNotFoundException e) {
continue; // keep on trying
} catch (NoClassDefFoundError err) {
continue; // keep on trying
} catch (Throwable e) {
continue; // keep on trying
}
}

// try default TRAX initialization
// if (DEBUG) System.err.println("trying default
TRAX");
TransformerFactory factory =
TransformerFactory.newInstance();
initFactory(factory);
if (DEBUG) System.err.println("using default
TRAX TransformerFactory=" + factory.getClass().getName());
return factory;
}
};
}

/**
* Creates and returns a new <code>XSLTransform</code> for the given
stylesheet.
*
* @param stylesheet
* the stylesheet to compile
* @throws XSLException
* if the XSLTransform can't be created, e.g. because of
an XSL syntax error.
*/
public XSLTransform createTransform(Document stylesheet) throws
XSLException {
return newTransform(stylesheet, (TransformerFactory)
threadLocal.get());
}

/**
* Creates and returns a new <code>XSLTransform</code> for the given
stylesheet.
*
* @param stylesheet
* the stylesheet to compile
* @throws IOException
* if an I/O error occurs while reading from the stream
* @throws ParsingException
* if the stylesheet is not well-formed XML
* @throws XSLException
* if the XSLTransform can't be created, e.g. because of
an XSL syntax error.
*/
public XSLTransform createTransform(File stylesheet) throws
XSLException, ParsingException, IOException {
return createTransform(new FileInputStream(stylesheet),
stylesheet.toURI());
}

/**
* Creates and returns a new <code>XSLTransform</code> for the given
stylesheet.
*
* @param stylesheet
* the stylesheet to compile
* @param baseURI
* the (absolute) baseURI of the stylesheet (may be
<code>null</code>)
* Need not be the stream's actual URI.
* @throws IOException
* if an I/O error occurs while reading from the stream
* @throws ParsingException
* if the stylesheet is not well-formed XML
* @throws XSLException
* if the XSLTransform can't be created, e.g. because of
an XSL syntax error.
*/
public XSLTransform createTransform(InputStream stylesheet, URI
baseURI) throws XSLException, ParsingException, IOException {
Document doc;
try {
if (baseURI == null)
doc =
BuilderPool.GLOBAL_POOL.getBuilder(false).build(stylesheet);
else
doc =
BuilderPool.GLOBAL_POOL.getBuilder(false).build(stylesheet,
baseURI.toASCIIString());
} catch (ValidityException e) {
throw new XMLException("should never happen", e);
}
return createTransform(doc);
}
/**
* Callback that creates and returns a new <code>XSLTransform</code>
for the given
* stylesheet and TransformerFactory.
* <p>
* Override this method if you want to create custom subclasses of
XSLTransform.
*
* @param stylesheet
* the stylesheet to compile
* @param transformerFactory
* the TransformerFactory
* @throws XSLException
* if no XSLTransform can be obtained for the given
stylesheet;
* in particular when the supplied stylesheet is not
* syntactically correct XSLT
*/
protected XSLTransform newTransform(Document stylesheet,
TransformerFactory transformerFactory) throws XSLException {
return new XSLTransform(stylesheet, new NodeFactory(),
transformerFactory);
}

/**
* Callback that returns a search list of fully qualified class names
of TRAX
* {@link TransformerFactory} implementations, given in order of
* preference from left to right. May return <code>null</code>.
* <p>
* Override this method for custom behaviour.
* This default implementation returns a search list for the most
popular
* TRAX implementations.
*
* @return a search list
*/
protected String[] getPreferredTransformerFactories() {
return DEFAULT_TRAX_FACTORIES;
}

/**
* Callback that initializes the supplied TransformerFactory with
application-specific
* attributes and a URIResolver, if so desired.
* <p>
* Override this method if you need custom attributes/resolvers. This
default
* implementation does nothing.
* <p>
* Note: Attributes and resolver are not part of the constructor
because they
* may well be stateful and mutable, hence it may well be unsafe to
share them
* among multiple XSLTransforms in a multi-threaded
* context. By providing this method, an application can create
attributes/resolvers
* as needed via straightforward subclassing/overriding of this
method.
*/
protected void initFactory(TransformerFactory factory) {
}

}


Archive powered by MHonArc 2.6.24.

Top of Page