http://www.developer.com/

Back to article

Optimizing Stylesheet Execution with the Java Transformation API for XML (TrAX)


August 27, 2002

Introduction

XSL (eXtensible Stylesheet Language) fulfills the need for transforming XML data into HTML, WML, or other XML formats. There is no easier way to parse and transform an XML document than XSL. However, the ease of use may come at a price. Because XSL is an interpreted language, there can be a performance tradeoff.

The Java Transformation API for XML (TrAX) can be used to invoke XSL stylesheets from Java programs. One of the nice features of TrAX is the ability to compile stylesheets and hold them in memory, dramatically improving performance.

This article will provide an introduction to some of the capabilities of TrAX:

  • We'll begin by using a simple example to demonstrate the basics of using the API.
  • Next, we'll learn how to compile stylesheets and keep them in memory to improve performance.
  • We'll conclude by demonstrating how to "hot deploy" stylesheets and refresh the compiled representation in memory.

A working understanding of Java and XML technology will be assumed.

A Simple Example

Let's dive right in with an example of using the Java Transformation API for XML. This example won't be optimized.

import javax.xml.transform.*;
import javax.xml.transform.stream.*;

public class SimpleTransform
{
    public static void main(String[] args) throws Exception
    {
        if (args.length != 3)
        {
            System.out.println("usage: SimpleTransform
                                <xmlFile> <xslFile> <outFile>");
            return;
        }
        String xmlFile = args[0];
        String xslFile = args[1];
        String outFile = args[2];
        
        StreamSource xslSource = new StreamSource(xslFile);

        TransformerFactory factory = 
             TransformerFactory.newInstance();

        Transformer transformer = 
             factory.newTransformer(xslSource);

        transformer.transform( new StreamSource(xmlFile)
                             , new StreamResult(outFile)
                             );
  }
}

SimpleTransform illustrates how the TrAX API can be used for file-based transformations. It takes an XML file and a stylesheet as inputs to create an output file representing the result of the transformation.

Notice that the StreamSource object is used to represent various types of input to the transformation. It is one of the implementations of the Source interface. StreamSource objects are constructed for the files containing the XSL stylesheet and the XML data to be transformed.

Similarly, the StreamResult object is used to represent the various types of output resulting from the transformation. It is one of the implementations of the Result interface. A StreamResult object is created for the output file of the transformation.

The TransformerFactory is used to get a Transformer object for the stylesheet. Its transform() method is used to transform the xml source into the result.

Important Note: The Transformer object provides a "per-thread" execution context for doing transformations. It is not thread safe. Although it is derived from the compiled stylesheet, it cannot be re-used between threads. Take it from someone who learned this the hard way.

Compiling Stylesheets

Now, let's build an example that will allow us to compile stylesheets and re-use them for multiple transformations. This will provide a significant performance boost to our transformations.

To explicitly compile stylesheets, we'll need to use the Templates object that TrAX provides. This object is a runtime representation of the transformation instructions. You can think of it as a compiled stylesheet. However, it is not represented in the file system like a .class file in Java. It is only represented in memory. Unlike the Transformer object, it is thread safe for concurrent usage once constructed.

We'll need to hold on to Templates objects for our stylesheets. Our strategy will be to hold a Map of Templates objects in a class variable. When transformations are requested, we'll first look into our Map and see if we already have a compiled stylesheet. If not, we'll add one.

import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import java.util.*;

public class XslTransformer
{
    private static Map CACHE = new Hashtable();
          // map of Templates objects
    private String xslFileName;
          // the filename of stylesheet and key to map
    private Transformer transformer; 

    public XslTransformer (String theXslFileName)
        throws TransformerConfigurationException
    {
        xslFileName  = theXslFileName;
        
        Templates templates = (Templates)CACHE.get(theXslFileName);
        if (templates == null)
        {
            TransformerFactory factory =
                       TransformerFactory.newInstance();

            templates = factory.newTemplates
                        (new StreamSource(xslFileName));

            CACHE.put(theXslFileName, templates);
        }
        transformer  = templates.newTransformer();
    }

    public void transform(Source xmlSource, Result result)
        throws TransformerException
    {
    transformer.transform(xmlSource, result);
    }

    public static void main(String[] args) throws Exception
    {
        if (args.length != 3)
        {
            System.out.println("usage: XslTransformer
                                <xmlFile> <xslFile> <outFile>");
            return;
        }
        String xmlFile = args[0];
        String xslFile = args[1];
        String outFile = args[2];
        
        XslTransformer xform = new XslTransformer(xslFile);
        xform.transform( new StreamSource(xmlFile)
                       , new StreamResult(outFile)
                       );
  }
}

In our constructor, the filename of the stylesheet is passed as an argument. We first look to see if we already have a Templates object for the stylesheet in the cache. If not, we ask the TransformerFactory for a new Templates object via newTemplates() rather than for a new Transformer object via newTransformer() as in the first example. Then we store the Templates object in the cache so that subsequent transformations using this stylesheet will use the version in the cache.

We are still using a Transformer object to do the transformation. However, we get the transformer by asking the Templates object for a new Transformer object via newTransformer(). Tranformations are performed by passing a Source and Result to the transform() method.

This example is quite an improvement over SimpleTransform. By using the Templates object, we will now only compile a given stylesheet the first time it is used. However, this comes at a cost. As long as XslTransformer stays loaded in memory, we'll never get a new version of the stylesheet. In a high availability situation, this might not be acceptable because you would have to bounce the application server or JVM to deploy a new version of the stylesheet.

Hot Deployed Stylesheets

Java Server Pages (JSP) technology handles hot deployments very nicely. Whenever the source code for a .jsp changes, a "stale" check detects the change and automatically recompiles the JSP.

With a little bit of work, we should be able to use the stale checking approach to hot deploy file-based stylesheets as well. Whenever we construct a XslTransformer object for a stylesheet in the cache, we'll check to see if the .xsl file has changed. If it has changed, we'll recompile it and cache a new Templates object. Note that to add the hot deploy capability, we'll use features of the Java language, not of the TrAX API.

First we'll build our own TemplateWrapper object to wrap the TrAX Templates object and provide stale checking. The important part of this inner class is the isStale() method that determines whether the stylesheet source has changed since it was last compiled.

    class TemplateWrapper
    {
        Templates stylesheet; // the compiled stylesheet
        File xslFile;         // represents the stylesheet source
        long timestamp;       // last compile time

        TemplateWrapper(Templates aStylesheet, String xslFileName)
        {
            stylesheet = aStylesheet;
            xslFile    = new File(xslFileName);
            timestamp  = xslFile.lastModified();
        }

        private boolean isStale()
        {
            return xslFile.lastModified() != timestamp;
        }
        
        Templates getStylesheet()
        {
            return stylesheet;
        }
    }

Now we need to make some changes to the constructor of XslTransformer to perform the stale check, and recompile the stylesheet when necessary.

    public XslTransformer(String theXslFileName)
        throws TransformerConfigurationException
    {
        xslFileName  = theXslFileName;
        
        TemplateWrapper stylesheet = (TemplateWrapper)CACHE.get
                                     (theXslFileName);
        if (stylesheet == null || stylesheet.isStale())
        {
            TransformerFactory factory =
                 TransformerFactory.newInstance();

            Templates template = factory.newTemplates
                                 (new StreamSource(xslFileName));

            stylesheet = new TemplateWrapper
                             (template, xslFileName);
            CACHE.put(theXslFileName, stylesheet);
        }
        transformer  = stylesheet.getStylesheet().newTransformer();
    }

Now the cache is storing TemplateWrapper objects instead of Templates objects. The isStale() method is used to determine when we need to refresh the cache with a new TemplateWrapper. Now we have both the performance boost of compiled stylesheets and the hot deploy capability for high availability.

Summary

The Java Transformation API for XML (TrAX) is relatively easy to use. Learning how to compile stylesheets is one of the advanced features worth mastering because it will result in a big performance boost to your application. The ability to hot deploy stylesheets could be lost with the performance boost. By adding some simple Java to the mix, we can get both the optimizations and the hot deploy capability. The XslTransformer object we created demonstrates this feature. It could be the start of a utility transformation class or even a transformation service used in a real production application to encapsulate the TrAX API, but that part is up to you.

Code Examples

To download the example java files, click here.

Additional Resources

Information on the Java Transformation API for XML can be found at http://xml.apache.org/xalan-j/trax.html.

Other Articles Written by Jeff Ryan

About the Author

Jeff Ryan is an architect for Hartford Financial Services. He has eighteen years of experience designing and developing automated solutions to business problems.
His current focus is on Java, XML, and Web Services technology. He may be reached at jryan@thehartford.com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date