first_page the funky knowledge base
personal notes from way, _way_ back and maybe today

XSLT: “Using Multiple URIResolvers for the Same Stylesheet”

A URIResolver, an interface defined in the javax.xml.transform package, is used to process a URI and create a Source object out of it. All Java developers working with XSLT have to decide which URIResolver they're going to use to resolve the URI.

In small examples and code snippets this is very simple. However, when working with large systems that are styling dynamic content, the decision on which URIResolver to use can be a very important one that will help create a more flexible and scalable application. This article will show how a URIResolver is utilized to resolve a URI and how to create a URIResolver that will allow these references to be resolved differently within the same stylesheet.

When we develop with Java and XSLT, a URIResolver is used to perform this function on the XSLT elements xsl:import and xsl:include as well as the XSL function document(). Commonly, relative URIs are used in these pieces of XSLT to reference other stylesheets or node sets. This will work beautifully if the developer is always aware of the base URI for the context in which he or she is currently developing. Keeping track of this can become complicated as more stylesheets are added to a system in different paths. For example, consider the folder structure shown in Figure 1.

If we're developing on a Windows box, we can import 02.xsl from 01.xsl

<xsl:stylesheet version="1.0" xmlns: xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:import href="c:/root/02.xsl"/>

and 03.xsl from 02.xsl.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:import href="c:/root/folder01/03.xsl"/>

This works great for development, but as soon as we move to the testing or production environment, where the base of the directory structure and platform is not the same, we have to go through and change all of the xsl:import, xsl:include, and document() calls in each of the XSL files. Of course, this is not an acceptable solution either because once the code is approved for production, no code modifications should be necessary to move from the testing to the production environment.

The URIResolver interface helps solve these problems. We can create a URIResolver that will always know where it should find the proper stylesheet. Before we see how this is done, let's make sure that we understand the URIResolver interface.

The URIResolver Interface The URIResolver interface exists in the javax.xml.transform package and is part of the JAXP API (see Figure 2). It has a single method, resolve, that any implementing class must provide. This method takes two String parameters, href and base, and returns a Source object. The href parameter corresponds to the href in the xsl:import or xsl:include elements. It is also the first parameter in the document() function. For example, in an import from a stylesheet <import href="02.xsl"/> the href parameter passed to the resolver is the string 02.xsl. The base parameter is not as straightforward. If the xsl:import, xsl:include, or document() call is made from within the primary document, then the base is the base URI of that document or the second parameter in the case of document(). However, if the message is sent from an external entity, then the base is the URI of that calling external entity. Let's consider the previous example of stylesheets 01.xsl, 02.xsl, and 03.xsl. The base for the import element in 01.xsl of <import href="c:/root/02.xsl"/> is c:/root/. Because we import 03.xsl from 02.xsl we are importing from an external entity, which makes the base for the import element <import href="c:/root/folder01/03.xsl"/> have a base of c:/root/02.xsl.

Now that we understand the URIResolver interface, we can put it to use in helping us resolve our problem. We would like to build a URIResolver that is smart enough to know the path to all of our stylesheets. Knowing that we will always deploy a WAR file helps us with a quick potential solution. If we can figure out what the path to our application is dynamically on any server, then we can solve the problem (see Listing 1).

With this URIResolver we are able to dynamically determine the path to our stylesheet using the ServletContext. When transforming, we can set this to be our URIResolver with:

TransformerFactory factory = TransformerFactory.newInstance(); factory.setURIResolver(newServletContextURIResolver (getServletContext(), "/style"));

Instead of worrying about changing references to the stylesheets, we can simply rely on the URIResolver to appropriately find the stylesheet documents. When the resolver is invoked, the ServletContext is used to find the real path to the directory containing the stylesheet documents. When the resolve method is called, the href will be appended to this path and a Source object created from that combined path. With this URIResolver in place the xsl:import, xsl:include, and document() need only to reference the relative path from the stylesheet directory. For example, if our stylesheets exist in the style directory on our application server, we can reference a stylesheet "mystyle.xsl" in the following manner: <import href="/mystyle.xsl"/>. We no longer have to specify the URI from the root of our system. We can start with a directory structure that we know will exist.

Using this solution for the problem of locating different stylesheets works great, but it does limit us to only one URIResolver for a transformation. Being able to reference multiple URIResolvers could be useful for retrieving stylesheets and documents from several different locations and for resolving custom stylesheets on the fly. How can we use multiple URIResolvers within one transformation of a particular stylesheet? There are several possible solutions to this problem, but we'll investigate one particular design that involves the ability to switch URIResolvers based upon the href and base passed to the installed URIResolver. This allows the stylesheet programmer to place key values in the xsl:import, xsl:include, and document() calls from the stylesheet that can cause different URIResolvers to be used. Consider the diagram in Figure 3.

Instead of using the ServletContextURIResolver, we will use the Dispatching URIResolver. The purpose of this DispatchingURIResolver is to simply dispatch the work to other URIResolvers. It does not do any resolving on its own. To accomplish this task, it makes use of two other classes, the DelimitedKeyParser and the BuilderURIResolverFactory. When the resolve method is called on the DispatchingURIResolver, it will use the DelimitedKeyParser to determine whether a special key has been specified for locating a unique resolver. If a key was found, then the BuilderURIResolverFactory creates a URIResolver that it associates with the key. If a key was not found, then the BuilderURIResolverFactory simply creates the default URIResolver. Assuming that there would be only a few cases in which a unique URIResolver would be needed within an application, it's important to provide this default case. This allows an application that uses a single URIResolver to use this pattern and keep itself open to future changes. The single URIResolver that is used would be installed as the default URIResolver and created every time. Once the factory is asked to create the appropriate URIResolver, the DispatchingURIResolver simply dispatches the work off to that new URIResolver. The resolve() method of the DispatchingURIResolver is shown in Listing 2.

Now that we know what's involved in dispatching to the proper URIResolver, let's take a look at an implementation of the DelimitedKeyParser and BuilderURIResolverFactory.

Let's assume that we'll use the ServletContextURIResolver as our default URIResolver. This means that, for the most part, our stylesheet imports will look like <import href="/02.xsl"/>. If our application must also access stylesheets that exist elsewhere on the Internet, we will need to use a different resolver. We'll call this resolver a RemoteStyleURIResolver. It will return the source of a stylesheet that exists on its server (see Figure 4).

We'll still need to specify an href to the proper stylesheet, but we must also identify it as a remote stylesheet. If we create an import statement that looks like <import href="remote.style#/style.xsl"/>, the DelimitedKeyParser will look for a # character delimiting the key and the href parts. In this example, the key would be remote.style and the href to be passed on to the URIResolver should be /style.xsl. The getKey method assumes that if it does not find the # character, then the key is the entire href. If this is incorrect, then the factory will not find any URIResolver associated with it and the default URIResolver will still be requested. The getHref method returns all of the String after the # character, or the entire String if no # is found. Finally, the getBase simply returns the entire base since we're not using it to determine any information in this example. If another delimiter is needed for the application instead of the default # character, then the new character delimiter can be set on the DelimitedKeyParser with the setDelimiter() method.

The BuilderURIResolverFactory has the responsibility of creating the appropriate URIResolver for a given key. To allow the concrete implementations to be dynamic if needed, the IURIResolverBuilder interface is used. This allows the creation of each URIResolver to be done in any manner that the application sees fit. The IURIResolverBuilder interface has a single method, build(), that is called from the BuilderURIResolverFactory create() and createDefault() methods. Adding IURIResolverBuilders to the Builder URIResolverFactory is very simple. There are two methods provided on the BuilderURIResolverFactory that allow the addition of an IURIResovlerBuilder implementation. The addBuilder (String key, IURIResolverBuilder builder) method is very straightforward, taking String as the first parameter and a builder as the second. The key identifies the partnering builder. When the factory is requested to create a new URIResolver through the create() method, it is given a key. This key will be used to retrieve the builder that was associated with it through the addBuilder method. The default IURIResolverBuilder can be specified either at the creation time of the factory through the constructor, or with the setDefaultBuilder(IURIResolverBuilder builder) method. This builder will be used when the createDefault method is invoked.

A simple, concrete implementation of the IURIResolverBuilder interface is one that uses the same instance of the given builder for each invocation of the resolve method. The SingleInstanceURIResolverBuilder stores the URIResolver it's given at its creation and returns it every time the build method is called. Special caution should be taken when using this builder to ensure that the URIResolver that is kept is not affected by a multithreaded environment.

Another implementation of a IURIResolverBuilder is the Reflective URIResovlerBuilder, which is thread-safe by nature. It takes a URIResolver as a parameter to its constructor. When its build() method is invoked, a new instance of the URIResolver is created and returned. The URIResolver class that is given to the ReflectiveURIResolver must have a default constructor for the build() method to successfully execute. Using the IURIResolverBuilder interface allows not only the generic creation of URIResolvers (as we see with the ReflectiveURIResolverBuilder), but also the dynamic creation of URIResolvers based upon specific application data (see Figure 5).

We now have the ability to use two different URIResolvers for the same transformation of a single stylesheet. We can make this all work by installing our DispatchingURIResolver as the resolver for the transformations. The following example shows how we can use this DispatchingURIResolver with the ServletContextURIResovler as the default resolver and the RemoteStyleURIResolver used for any key found in the xsl:import, xsl:include, or document() calls using the key "remote:style" (see Listing 3).

This implementation is very simple, straightforward, and ready for immediate use. If the DelimitedKeyParser or the BuilderURIResolverFactory implementations do not meet the needs of an application, developers may choose to implement their own implementations. This is made possible by the use of the IKeyParser and IURIResolverFactory interfaces within the DispatchingURIResolver. While the provided BuilderURIResolverFactory and DelimiterKeyParser should meet the needs of most situations, the solution is flexible enough to take other forms because of the use of these interfaces (see Figure 6).

The use of the DispatchingURIResolver gives developers creating applications using XSLT the flexibility to use more than one URIResolver for a Transformer. This opens up the door for many possibilities, including pulling stylesheets from different sources and generating dynamic stylesheets.

[http://xml.sys-con.com/node/40655]

mod date: 2009-08-27T17:26:52.000Z