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.