Bug #4019
closedProblems with reuse of Transformer instances
100%
Description
Starting with SaxonHE version 9.9.0-1 there is a problem with reuse of Transformer instances. If you have variables set in your XSLT outside of a template they are not cleared when you reuse the Transformer with another XML input.
The attached code example illustrates the issue. In the XSLT there is a variable populated with a value selected from the XML. The same Transformer instance is then used for two transformations in a row with different XML input. Both times the same value is output.
Files
Updated by Michael Kay about 6 years ago
- Category set to JAXP Java API
- Assignee set to Michael Kay
- Priority changed from High to Normal
Thanks for reporting this.
As a workaround, please create a new Transformer for each transformation.
My recommendation is to compile the stylesheet once to a Templates object (using factory.newTemplates()) and then create a new Transformer for each transformation using Templates.newTransformer(). The JAXP specification says that what you are doing should work, so this is indeed a bug, but with Saxon it is generally cleaner to let the garbage collector dispose of the resources held by the Transformer object once it's finished.
Updated by Michael Kay about 6 years ago
Problem reproduced with JUnit test jaxptest/TransformTest/testTransformerReuse
Updated by Michael Kay about 6 years ago
- Status changed from New to In Progress
Getting this right is trickier than it might appear, because the JAXP Transformer
is layered on top of the s9api XsltTransformer
, which in turn is layered on top of the internal XsltControlle
r; in effect we need to work out how serial reuse should behave at each of these three levels. We should also get it right for the Xslt30Transformer
while we're about it. (One option is that the JAXP Transformer
should create a new s9api XsltTransformer
for each transformation, which might simplify things a little, but it doesn't solve the whole problem).
The problem is that calling transform()
has side-effects on the state of the XsltTransformer
(and the underying XsltController
) which it's hard to undo afterwards. For example, it causes XsltTransformer.initialSource
to be set, exactly as if the user had called setSource()
directly, and we cannot afterwards tell whether the setting was due to an explicit or implicit call. Which raises another complication, namely that if setSource()
supplies a DOMSource
, then it is reasonable to use the same DOMSource
again for subsequent transformations, but if a SAXSource
or StreamSource
is supplied, then it is impossible to use it again, because it is consumed by use.
The Javadoc for XsltTransformer
says that serial reuse is allowed, and that running the tranformation "does not change the context that has been established". This is clearly an over-simplification. For example, it also says (under getBaseOutputURI()) "If no value has been set explicitly, then the method returns null if called before the transformation, or the computed default base output URI if called after the transformation."
Updated by Michael Kay about 6 years ago
- Fixed in Maintenance Release 9.7.0.10 added
As a start, I have been documenting more carefully in XsltTransformer
the effect on the state of the XsltTransformer
of calling the transform()
method; in particular the effect on the Source
and Destination
(both of which, in the general case, can only be used once).
I think it's best if transform()
doesn't modify the value of the initialSource
variable, so I have changed this. This means that, for example, if initialSource is supplied as a StreamSource
, it will still be the same StreamSource
object after the transformation, rather than (as now) being the document node of the tree built from that StreamSource
. (This means that currently, an application might be able to supply a StreamSource and then transform it twice; after this change this will no longer be possible. But it only works currently by accident, and is undocumented).
The next question is the state of the XsltController
after a call on callTemplate()
or applyTemplatesToSource()
or applyStreamingTemplates()
; also, XsltController.transform()
explicitly calls XsltController.setGlobalContextItem()
(which is the immediate cause of the trouble in this bug).
Because the XsltController
carries such a lot of state, and it's hard to ensure that this is unaffected by a transformation, it's tempting to suggest that the s9api XsltTransformer
and Xslt30Transformer
should only create an XsltController
on demand, creating a new one for each transformation. At first sight this looks like a rather extensive change, but I shall experiment with it.
Updated by Michael Kay about 6 years ago
On reflection, I think there are probably many applications that rely on calling XsltTransformer.getUnderlyingController()
to achieve advanced effects, so changing the XsltTransformer
to create the Controller dynamically would probably be too disruptive a change.
Updated by Michael Kay about 6 years ago
- Status changed from In Progress to Resolved
- Fix Committed on Branch 9.9 added
I have solved this particular test case by tweaking XsltTransformer.transform()
so that if the global context item in the XsltController
is null on entry to the method, then it is reset to null on completion of the method (whether the transformation succeeds or fails).
With the Xslt30Transformer
we don't have the same complications because we disallow serial reuse.
Fixed as described on 9.9 branch. I have checked that the problem does not exist on 9.8.
Updated by O'Neil Delpratt almost 6 years ago
- % Done changed from 0 to 100
- Fixed in Maintenance Release 9.9.1.1 added
- Fixed in Maintenance Release deleted (
9.7.0.10)
Bug fix applied to the Saxon 9.9.1.1 maintenance release.
Updated by O'Neil Delpratt almost 6 years ago
- Status changed from Resolved to Closed
Please register to edit this issue