Project

Profile

Help

Bug #5886

closed

Serializer 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

Added by Martin Honnen about 1 year ago. Updated 8 months ago.

Status:
Resolved
Priority:
Normal
Assignee:
-
Category:
s9api API
Sprint/Milestone:
Start date:
2023-02-22
Due date:
% Done:

0%

Estimated time:
Legacy ID:
Applies to branch:
11
Fix Committed on Branch:
Fixed in Maintenance Release:
Platforms:
.NET, Java

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.

Actions #1

Updated by Michael Kay about 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.

Actions #2

Updated by Michael Kay about 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).

Actions #3

Updated by Martin Honnen about 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.

Actions #4

Updated by Michael Kay about 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().

Actions #5

Updated by Michael Kay 8 months 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.

Please register to edit this issue

Also available in: Atom PDF