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:56:41 -0800

Resending without the second attachment (the message size was too large for the mailing list):

Begin forwarded message:

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 + "]";
}


}


Archive powered by MHonArc 2.6.24.

Top of Page