Bug #5886
closedSerializer of s9api in Saxon 11 for json and adaptive output method seems to close underlying stream while that doesn't happen for output method xml
100%
Description
Based on my findings in https://saxonica.plan.io/boards/3/topics/9295 and what I fill explain further it appears Saxon 11 has a bug where the use of an s9api Serializer
over an OutputStream
as the Destination
of an Xslt30Transformer
's callTemplate
or applyTemplates
closes the underlying stream for output methods json and adaptive while that doesn't happen (as I think it is supposed to do) for output method xml.
So for some sample code like
package org.example;
import net.sf.saxon.s9api.*;
import javax.xml.transform.stream.StreamSource;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;
public class Main {
public static void main(String[] args) throws IOException, SaxonApiException {
Processor processor = new Processor(false);
String xsltXmlOutput = "<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n" +
" version=\"3.0\"\n" +
" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\n" +
" exclude-result-prefixes=\"#all\"\n" +
" expand-text=\"yes\">\n" +
"\n" +
" <xsl:output method=\"xml\" indent=\"yes\"/>\n" +
"\n" +
" <xsl:template match=\"/\" name=\"xsl:initial-template\">\n" +
" <test>Run at {current-dateTime()} with {system-property('xsl:product-name')} {system-property('xsl:product-version')}</test>\n" +
" <xsl:comment>Run at {current-dateTime()} with {system-property('xsl:product-name')} {system-property('xsl:product-version')}</xsl:comment>\n" +
" </xsl:template>\n" +
" \n" +
"</xsl:stylesheet>";
XsltCompiler xsltCompiler = processor.newXsltCompiler();
XsltExecutable xsltExecutable = xsltCompiler.compile(new StreamSource(new StringReader(xsltXmlOutput)));
Xslt30Transformer xslt30Transformer = xsltExecutable.load30();
FileOutputStream stream = new FileOutputStream("xml-fragment-result.xml");
xslt30Transformer.callTemplate(null, processor.newSerializer(stream));
xslt30Transformer = xsltExecutable.load30();
xslt30Transformer.callTemplate(null, processor.newSerializer(stream));
stream.close();
String xsltJsonOutput = "<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n" +
" version=\"3.0\"\n" +
" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\n" +
" exclude-result-prefixes=\"#all\"\n" +
" expand-text=\"yes\">\n" +
" \n" +
" <xsl:output method=\"json\" indent=\"yes\"/>\n" +
"\n" +
" <xsl:template match=\"/\" name=\"xsl:initial-template\">\n" +
" <xsl:sequence\n" +
" select=\"map { 'tested-at' : current-dateTime(), 'with' : system-property('xsl:product-name') || ' ' || system-property('xsl:product-version') }\"/>\n" +
" </xsl:template>\n" +
" \n" +
"</xsl:stylesheet>";
xsltCompiler = processor.newXsltCompiler();
xsltExecutable = xsltCompiler.compile(new StreamSource(new StringReader(xsltJsonOutput)));
xslt30Transformer = xsltExecutable.load30();
stream = new FileOutputStream("JsonResults.json");
xslt30Transformer.callTemplate(null, processor.newSerializer(stream));
xslt30Transformer = xsltExecutable.load30();
xslt30Transformer.callTemplate(null, processor.newSerializer(stream));
stream.close();
String xsltAdaptiveOutput = "<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"\n" +
" version=\"3.0\"\n" +
" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\n" +
" exclude-result-prefixes=\"#all\"\n" +
" expand-text=\"yes\">\n" +
" \n" +
" <xsl:output method=\"adaptive\" indent=\"yes\"/>\n" +
"\n" +
" <xsl:template match=\"/\" name=\"xsl:initial-template\">\n" +
" <xsl:sequence\n" +
" select=\"map { 'tested-at' : current-dateTime(), 'with' : system-property('xsl:product-name') || ' ' || system-property('xsl:product-version') }\"/>\n" +
" </xsl:template>\n" +
" \n" +
"</xsl:stylesheet>";
xsltExecutable = xsltCompiler.compile(new StreamSource(new StringReader(xsltAdaptiveOutput)));
xslt30Transformer = xsltExecutable.load30();
stream = new FileOutputStream("AdaptiveResults.txt");
xslt30Transformer.callTemplate(null, processor.newSerializer(stream));
xslt30Transformer = xsltExecutable.load30();
xslt30Transformer.callTemplate(null, processor.newSerializer(stream));
stream.close();
}
}
the XML result file xml-fragment-result.xml
has the output of the two transformations e.g.
<?xml version="1.0" encoding="UTF-8"?>
<test>Run at 2023-02-22T14:18:42.377+01:00 with SAXON HE 11.5</test>
<!--Run at 2023-02-22T14:18:42.377+01:00 with SAXON HE 11.5-->
<?xml version="1.0" encoding="UTF-8"?>
<test>Run at 2023-02-22T14:18:42.379+01:00 with SAXON HE 11.5</test>
<!--Run at 2023-02-22T14:18:42.379+01:00 with SAXON HE 11.5-->
while for the JsonResults.json
and the AdaptiveResults.txt
the output only contains the result of the first transformation e.g.
{ "with":"SAXON HE 11.5", "tested-at":"2023-02-22T14:18:42.394+01:00" }
and e.g.
map{"with":"SAXON HE 11.5","tested-at":xs:dateTime("2023-02-22T14:18:42.401+01:00")}
And for the adaptive sample the problem also manifests itself as an error
Error
java.io.IOException: Stream Closed: Stream Closed
Exception in thread "main" net.sf.saxon.s9api.SaxonApiException: java.io.IOException: Stream Closed
at net.sf.saxon.s9api.Xslt30Transformer.callTemplate(Xslt30Transformer.java:493)
at org.example.Main.main(Main.java:101)
Caused by: net.sf.saxon.trans.XPathException: java.io.IOException: Stream Closed
at net.sf.saxon.serialize.AdaptiveEmitter.close(AdaptiveEmitter.java:332)
at net.sf.saxon.event.ProxyReceiver.close(ProxyReceiver.java:104)
at net.sf.saxon.event.CloseNotifier.close(CloseNotifier.java:36)
at net.sf.saxon.event.ComplexContentOutputter.close(ComplexContentOutputter.java:739)
at net.sf.saxon.trans.XsltController.callTemplate(XsltController.java:879)
at net.sf.saxon.s9api.Xslt30Transformer.callTemplate(Xslt30Transformer.java:485)
... 1 more
Caused by: java.io.IOException: Stream Closed
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:326)
at net.sf.saxon.serialize.UTF8Writer._flushBuffer(UTF8Writer.java:577)
at net.sf.saxon.serialize.UTF8Writer.close(UTF8Writer.java:96)
at net.sf.saxon.serialize.AdaptiveEmitter.close(AdaptiveEmitter.java:330)
... 6 more
The problem is particularly crippling if you use a Serializer
over System.out
for some transformation output as at that point it seems after the first transformation to System.out with json output method any other attempt to write/print to it is swallowed, as occurs in https://github.com/martin-honnen/SaxonJson2JsonExample1.
The problem doesn't occur with Saxon 12.
Updated by Michael Kay over 1 year ago
I've tested this hypothesis by wrapping a FilterOutputStream around the supplied stream, and monitoring calls to close the FilterOutputStream - it does not get closed until the user-written client code closes it itself. So this explanation for the observed results seems to be wrong.
Updated by Michael Kay over 1 year ago
Sorry, that test was run by mistake against Saxon 12. In Saxon 11, the output stream is indeed closed.
The fix to prevent it being closed was made during the investigation of bug #5552 - see https://saxonica.plan.io/issues/5552#note-18
I guess the reason I didn't make this change on the 11 branch was that I felt it might be destabilising - this is an area where it's very hard to be sure that changes are thoroughly tested (especially as failure to close files often has no adverse effect except on Windows, and that's not our main development platform).
Updated by Martin Honnen over 1 year ago
This still sometimes crippling, today I was trying to serialize arbitrary XPath results where the default xml output method doesn't work so I used adaptive instead but that way with Saxon 11 my StringWriter seems to be closed after I have tried to serialize an item or value.
Updated by Michael Kay over 1 year ago
As a workaround until we get another Saxon 11 maintenance release out, you could send output to a FilterOutputStream that suppresses the calls on close().
Updated by Michael Kay over 1 year ago
- Status changed from New to Resolved
- Platforms .NET added
The fix described in bug #5222 comment 18 has now been retrofitted to the 11.x branch, for release with 11.6.
Updated by Debbie Lockett 5 months ago
- Status changed from Resolved to Closed
- % Done changed from 0 to 100
- Fix Committed on Branch 11 added
- Fixed in Maintenance Release 11.6 added
Belatedly marking closed - the bug fix was applied in the Saxon 11.6 maintenance release.
Please register to edit this issue