Greetings again,

There is a bug in the TransformRepresentation.getTransformer() method
involving the wrong setURIResolver method being called.  The method in
question:

   public Transformer getTransformer() throws IOException {
       if (this.transformer == null) {
           try {
               // Prepare the XSLT transformer documents
               StreamSource transformSheet = new StreamSource(
                       getTransformSheet().getStream());

               // Create a new transformer as they are not thread safe
               this.transformer = TransformerFactory.newInstance()
                       .newTransformer(transformSheet);

               // Set the URI resolver
               transformer.setURIResolver(getURIResolver());
           } catch (TransformerConfigurationException tce) {
               throw new IOException("Transformer configuration exception.
"
                       + tce.getMessage());
           } catch (TransformerFactoryConfigurationError tfce) {
               throw new IOException(
                       "Transformer factory configuration exception. "
                               + tfce.getMessage());
           }
       }

       return this.transformer;
   }

The this.transformer assignment calls for an instance of the
TransformerFactory, then calls for a new Transformer handing it the
transformsheet in the process.  At this point in execution, compiling of the
transform sheet is done and any <xsl:include>,<xsl:import> or document()
functions will use the default URIResolver. But in this case, the URIResolver that is intended to be used is set AFTER the stylesheet is compiled, which is after include/import tags need to be resolved.

So what is being used as a URIResolver? Null is the value, and the XSLT spec says that if it's null then the base that the XSLT implementers should use is the address of the current stylesheet as the base reference. In some contexts of the Transformer, the stylesheet is passed as a file object so the transformer implementation has a chance to resolve include/imports when the URIResolver is null. In the TransformRepresentation case, it only sees a stream so there is a different default base address, which is usually the working address of the program that is creating Transformer objects. In the case of using the ServerServlet, for the most part this base address is going to be the application container address.. In the netbeans bundled tomcat it resolves to the tomcat bin folder. With Resin it resolves somewhat more correctly to the webapps/application root directory.

The next problem is that Transformer.setURIResolver is for setting the resolver for ONLY document() function calls in a stylesheet, which are dynamic and resolved at runtime. Include/import tags are static and resolved at compile time. The TransformerFactory.setURIResolver is the correct method, it sets the resolver for include/import and document() function. So the modified code would look like this:

 @Override
 public Transformer getTransformer() throws IOException {
   /* There is no way to set the transformer in this override
    * since their is no protected setTransformer method.
    * A new Transformer is created for each call.     */
   Transformer result = null;
   try {
     // Prepare the XSLT transformer documents
     StreamSource transformSheet = new StreamSource(
         getTransformSheet().getStream());

     // Get a transformer factory.
     TransformerFactory tfactory = TransformerFactory.newInstance();
     // Set the URI resolver before compiling the stylesheet
     // since all it ever sees is a stream, not a file/path object.
     tfactory.setURIResolver(getURIResolver());
     // Create a new transformer as they are not thread safe
     result = tfactory.newTransformer(transformSheet);

   } catch (TransformerConfigurationException tce) {
     throw new IOException("Transformer configuration exception. "
         + tce.getMessage());
   } catch (TransformerFactoryConfigurationError tfce) {
     throw new IOException(
         "Transformer factory configuration exception. "
         + tfce.getMessage());
   }
   return result;
 }

Now the behavior actually invokes the default URIResolver for TransformRepresentation, which is the embedded member class ContextResolver. Line 184-185 there seems like an error in forgetting to resolve the base address:

               Response response = this.context.getDispatcher().get(
                       targetRef.toString());
should be:
               Response response = this.context.getDispatcher().get(
                       targetRef.getTargetRef().toString());

Otherwise the base address is never resolved. This base address String value that is passed into the URIResolver is, I think, exclusively the explicitly called base value that is encountered in xslt tags that contain xml:base="path to base" attributes. I'm not positive on that. This is about where I am right now. I think more test cases are needed for the ContextResolver class maybe. Some transformer implementations may pass the base parameter to the resolver as null or as an empty string. Right now there is no check for this but I would think they both mean the same thing. I'm currently unable to get line 184-185 to return a response with a non-null entity even with a valid reference anyway, so I'm still digging around.

Right now I suggest using a custom URIResolver when you need includes/imports in your xslt files or you are running under the ServerServlet in a app container. Unfortunately, there is no TransformRepresentation.setURIResolver as it is locked into it's custom ContextResolver during construction. So until this is remedied as well, you need to subclass TransformRepresentation anyway and override the entire getTransformer() method to accept your custom resolver.

Joe.

Reply via email to