-
Bug
-
Resolution: Unresolved
-
P4
-
8, 11, 17, 21, 22, 23
-
generic
-
generic
ADDITIONAL SYSTEM INFORMATION :
> java -version
java version "21.0.2" 2024-01-16 LTS
Java(TM) SE Runtime Environment (build 21.0.2+13-LTS-58)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.2+13-LTS-58, mixed mode, sharing)
A DESCRIPTION OF THE PROBLEM :
After transforming an XML source to a StAXResult, the produced StartDocument event location is null which further causes NPE in at least one scenario. The XMLEvent.getLocation() API doesn't suggest it could be null, so it should never be:
* https://docs.oracle.com/en/java/javase/21/docs/api/java.xml/javax/xml/stream/events/XMLEvent.html#getLocation()
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Run the provided test case: `java TransformPipelineTest.java`
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No failure:
```
Event buffer:
StartDocumentEvent(location: <non-null>)
...
<hello>World</hello>
```
ACTUAL -
Failing:
```
Event buffer:
StartDocumentEvent(location: null)
StartElementEvent(location: DummyLocation)
CharacterEvent(location: DummyLocation)
EndElementEvent(location: DummyLocation)
EndDocumentEvent(location: DummyLocation)
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "javax.xml.stream.Location.getSystemId()" because the return value of "javax.xml.stream.events.XMLEvent.getLocation()" is null
at java.xml/javax.xml.transform.stax.StAXSource.<init>(StAXSource.java:122)
at TransformPipelineTest.main(TransformPipelineTest.java:52)
```
---------- BEGIN SOURCE ----------
//package ;
import java.io.StringReader;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.Optional;
import java.util.Queue;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.Location;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stax.StAXResult;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public class TransformPipelineTest {
public static void main(String[] args) throws Exception {
Source source = new StreamSource(new StringReader("<hello>World</hello>"));
Transformer transformation1 = sampleTransformation();
//Transformer transformation1 = defaultIdentity();
XMLEventBufferWriter bufferWriter = new XMLEventBufferWriter();
Result intermediate = new StAXResult(bufferWriter);
transformation1.transform(source, intermediate);
System.out.println("Event buffer:");
for (XMLEvent event : bufferWriter.getBuffer()) {
Location location = event.getLocation();
System.out.printf("\t%s(location: %s)%n",
event.getClass().getSimpleName(),
(location == null) ? "null" :
location.getClass().getSimpleName());
}
System.out.println();
//Transformer transformation2 = sampleTransformation();
Transformer transformation2 = defaultIdentity();
XMLEventBufferReader bufferReader = new XMLEventBufferReader(bufferWriter.getBuffer());
transformation2.transform(new StAXSource(bufferReader),
//transformation2.transform(new NullLocationStAXSource(bufferReader),
new StreamResult(System.out));
}
// An identity transform, really
private static final String SAMPLE_XSLT = "<xsl:stylesheet "
+ "version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>"
+ "<xsl:template match='@*|node()'>"
+ "<xsl:copy>"
+ "<xsl:apply-templates select='@*|node()' />"
+ "</xsl:copy>"
+ "</xsl:template>"
+ "</xsl:stylesheet>";
static Transformer sampleTransformation()
throws TransformerConfigurationException {
return transformation(Optional
.of(new StreamSource(new StringReader(SAMPLE_XSLT))));
}
static Transformer defaultIdentity()
throws TransformerConfigurationException {
return transformation(Optional.empty());
}
private static Transformer transformation(Optional<Source> xslt)
throws TransformerConfigurationException {
TransformerFactory tf = TransformerFactory.newInstance();
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Transformer transformer = xslt.isPresent()
? tf.newTransformer(xslt.get())
: tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
return transformer;
}
} // class TransformPipelineTest
class XMLEventBufferReader implements XMLEventReader {
private Iterator<XMLEvent> events;
private XMLEvent nextEvent;
public XMLEventBufferReader(Iterable<XMLEvent> source) {
this.events = source.iterator();
}
@Override
public Object getProperty(String name) throws IllegalArgumentException {
throw notImplementedException();
}
@Override
public boolean hasNext() {
return events.hasNext();
}
@Override
public Object next() {
return nextEvent();
}
@Override
public XMLEvent nextEvent() {
XMLEvent next = nextEvent;
if (next == null) {
next = events.next();
} else {
nextEvent = null;
}
return next;
}
@Override
public XMLEvent peek() {
XMLEvent next = nextEvent;
if (next == null) {
if (!hasNext()) return null;
next = nextEvent();
nextEvent = next;
}
return next;
}
@Override
public XMLEvent nextTag() throws XMLStreamException {
throw notImplementedException();
}
@Override
public String getElementText() throws XMLStreamException {
throw notImplementedException();
}
private static IllegalStateException notImplementedException() {
return new IllegalStateException("Not implemented");
}
@Override
public void close() {
// no-op
}
} // class XMLEventBufferReader
class XMLEventBufferWriter implements XMLEventWriter {
private final Queue<XMLEvent> buffer;
public XMLEventBufferWriter() {
buffer = new ArrayDeque<>();
}
public Iterable<XMLEvent> getBuffer() {
return buffer;
}
@Override
public void add(XMLEvent event) {
buffer.add(event);
}
@Override
public void add(XMLEventReader reader) {
throw notImplementedException();
}
@Override
public String getPrefix(String uri) {
throw notImplementedException();
}
@Override
public void setPrefix(String prefix, String uri) {
throw notImplementedException();
}
@Override
public void setDefaultNamespace(String uri) {
throw notImplementedException();
}
@Override
public void setNamespaceContext(NamespaceContext context) {
throw notImplementedException();
}
@Override
public NamespaceContext getNamespaceContext() {
throw notImplementedException();
}
private static IllegalStateException notImplementedException() {
return new IllegalStateException("Not implemented");
}
@Override
public void flush() {
// no-op
}
@Override
public void close() {
// no-op
}
} // class XMLEventBufferWriter
class NullLocationStAXSource extends StAXSource {
private final XMLEventReader xmlEventReader;
public NullLocationStAXSource(XMLEventReader xmlEventReader)
throws XMLStreamException {
this("tag:example.net,2024-03:dummy", xmlEventReader);
}
public NullLocationStAXSource(String systemId, XMLEventReader xmlEventReader)
throws XMLStreamException {
super(dummyReader(systemId));
this.xmlEventReader = xmlEventReader;
}
@Override
public XMLEventReader getXMLEventReader() {
return xmlEventReader;
}
private static XMLEventReader dummyReader(String systemId)
throws XMLStreamException {
XMLInputFactory xif = XMLInputFactory.newInstance();
return xif.createXMLEventReader(systemId, new StringReader("<dummy />"));
}
} // class NullLocationStAXSource
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
More of a hack than an actual solution – see the NullLocationStAXSource class in the provided test case. It doesn't work with the Woodstox StAX provider plugged into the runtime:
```
Event buffer:
StartDocumentEventImpl(location: null)
SimpleStartElement(location: null)
CharactersEventImpl(location: null)
EndElementEventImpl(location: null)
EndDocumentEventImpl(location: null)
Exception in thread "main" javax.xml.transform.TransformerException: javax.xml.transform.TransformerException: java.lang.NullPointerException: Cannot invoke "javax.xml.stream.Location.getSystemId()" because the return value of "javax.xml.stream.events.XMLEvent.getLocation()" is null
...
java.lang.NullPointerException: Cannot invoke "javax.xml.stream.Location.getSystemId()" because the return value of "javax.xml.stream.events.XMLEvent.getLocation()" is null
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.StAXEvent2SAX$1.getSystemId(StAXEvent2SAX.java:286)
at java.xml/com.sun.org.apache.xml.internal.dtm.ref.sax2dtm.SAX2DTM.setDocumentLocator(SAX2DTM.java:1610)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.StAXEvent2SAX.handleStartDocument(StAXEvent2SAX.java:275)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.StAXEvent2SAX.bridge(StAXEvent2SAX.java:160)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.StAXEvent2SAX.parse(StAXEvent2SAX.java:114)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.dom.XSLTCDTMManager.getDTM(XSLTCDTMManager.java:294)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.dom.XSLTCDTMManager.getDTM(XSLTCDTMManager.java:214)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.getDOM(TransformerImpl.java:582)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:783)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:395)
at TransformPipelineTest.main(TransformPipelineTest.java:53)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:484)
at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:208)
at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:135)
```
FREQUENCY : always
> java -version
java version "21.0.2" 2024-01-16 LTS
Java(TM) SE Runtime Environment (build 21.0.2+13-LTS-58)
Java HotSpot(TM) 64-Bit Server VM (build 21.0.2+13-LTS-58, mixed mode, sharing)
A DESCRIPTION OF THE PROBLEM :
After transforming an XML source to a StAXResult, the produced StartDocument event location is null which further causes NPE in at least one scenario. The XMLEvent.getLocation() API doesn't suggest it could be null, so it should never be:
* https://docs.oracle.com/en/java/javase/21/docs/api/java.xml/javax/xml/stream/events/XMLEvent.html#getLocation()
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Run the provided test case: `java TransformPipelineTest.java`
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No failure:
```
Event buffer:
StartDocumentEvent(location: <non-null>)
...
<hello>World</hello>
```
ACTUAL -
Failing:
```
Event buffer:
StartDocumentEvent(location: null)
StartElementEvent(location: DummyLocation)
CharacterEvent(location: DummyLocation)
EndElementEvent(location: DummyLocation)
EndDocumentEvent(location: DummyLocation)
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "javax.xml.stream.Location.getSystemId()" because the return value of "javax.xml.stream.events.XMLEvent.getLocation()" is null
at java.xml/javax.xml.transform.stax.StAXSource.<init>(StAXSource.java:122)
at TransformPipelineTest.main(TransformPipelineTest.java:52)
```
---------- BEGIN SOURCE ----------
//package ;
import java.io.StringReader;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.Optional;
import java.util.Queue;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.Location;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stax.StAXResult;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public class TransformPipelineTest {
public static void main(String[] args) throws Exception {
Source source = new StreamSource(new StringReader("<hello>World</hello>"));
Transformer transformation1 = sampleTransformation();
//Transformer transformation1 = defaultIdentity();
XMLEventBufferWriter bufferWriter = new XMLEventBufferWriter();
Result intermediate = new StAXResult(bufferWriter);
transformation1.transform(source, intermediate);
System.out.println("Event buffer:");
for (XMLEvent event : bufferWriter.getBuffer()) {
Location location = event.getLocation();
System.out.printf("\t%s(location: %s)%n",
event.getClass().getSimpleName(),
(location == null) ? "null" :
location.getClass().getSimpleName());
}
System.out.println();
//Transformer transformation2 = sampleTransformation();
Transformer transformation2 = defaultIdentity();
XMLEventBufferReader bufferReader = new XMLEventBufferReader(bufferWriter.getBuffer());
transformation2.transform(new StAXSource(bufferReader),
//transformation2.transform(new NullLocationStAXSource(bufferReader),
new StreamResult(System.out));
}
// An identity transform, really
private static final String SAMPLE_XSLT = "<xsl:stylesheet "
+ "version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>"
+ "<xsl:template match='@*|node()'>"
+ "<xsl:copy>"
+ "<xsl:apply-templates select='@*|node()' />"
+ "</xsl:copy>"
+ "</xsl:template>"
+ "</xsl:stylesheet>";
static Transformer sampleTransformation()
throws TransformerConfigurationException {
return transformation(Optional
.of(new StreamSource(new StringReader(SAMPLE_XSLT))));
}
static Transformer defaultIdentity()
throws TransformerConfigurationException {
return transformation(Optional.empty());
}
private static Transformer transformation(Optional<Source> xslt)
throws TransformerConfigurationException {
TransformerFactory tf = TransformerFactory.newInstance();
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Transformer transformer = xslt.isPresent()
? tf.newTransformer(xslt.get())
: tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
return transformer;
}
} // class TransformPipelineTest
class XMLEventBufferReader implements XMLEventReader {
private Iterator<XMLEvent> events;
private XMLEvent nextEvent;
public XMLEventBufferReader(Iterable<XMLEvent> source) {
this.events = source.iterator();
}
@Override
public Object getProperty(String name) throws IllegalArgumentException {
throw notImplementedException();
}
@Override
public boolean hasNext() {
return events.hasNext();
}
@Override
public Object next() {
return nextEvent();
}
@Override
public XMLEvent nextEvent() {
XMLEvent next = nextEvent;
if (next == null) {
next = events.next();
} else {
nextEvent = null;
}
return next;
}
@Override
public XMLEvent peek() {
XMLEvent next = nextEvent;
if (next == null) {
if (!hasNext()) return null;
next = nextEvent();
nextEvent = next;
}
return next;
}
@Override
public XMLEvent nextTag() throws XMLStreamException {
throw notImplementedException();
}
@Override
public String getElementText() throws XMLStreamException {
throw notImplementedException();
}
private static IllegalStateException notImplementedException() {
return new IllegalStateException("Not implemented");
}
@Override
public void close() {
// no-op
}
} // class XMLEventBufferReader
class XMLEventBufferWriter implements XMLEventWriter {
private final Queue<XMLEvent> buffer;
public XMLEventBufferWriter() {
buffer = new ArrayDeque<>();
}
public Iterable<XMLEvent> getBuffer() {
return buffer;
}
@Override
public void add(XMLEvent event) {
buffer.add(event);
}
@Override
public void add(XMLEventReader reader) {
throw notImplementedException();
}
@Override
public String getPrefix(String uri) {
throw notImplementedException();
}
@Override
public void setPrefix(String prefix, String uri) {
throw notImplementedException();
}
@Override
public void setDefaultNamespace(String uri) {
throw notImplementedException();
}
@Override
public void setNamespaceContext(NamespaceContext context) {
throw notImplementedException();
}
@Override
public NamespaceContext getNamespaceContext() {
throw notImplementedException();
}
private static IllegalStateException notImplementedException() {
return new IllegalStateException("Not implemented");
}
@Override
public void flush() {
// no-op
}
@Override
public void close() {
// no-op
}
} // class XMLEventBufferWriter
class NullLocationStAXSource extends StAXSource {
private final XMLEventReader xmlEventReader;
public NullLocationStAXSource(XMLEventReader xmlEventReader)
throws XMLStreamException {
this("tag:example.net,2024-03:dummy", xmlEventReader);
}
public NullLocationStAXSource(String systemId, XMLEventReader xmlEventReader)
throws XMLStreamException {
super(dummyReader(systemId));
this.xmlEventReader = xmlEventReader;
}
@Override
public XMLEventReader getXMLEventReader() {
return xmlEventReader;
}
private static XMLEventReader dummyReader(String systemId)
throws XMLStreamException {
XMLInputFactory xif = XMLInputFactory.newInstance();
return xif.createXMLEventReader(systemId, new StringReader("<dummy />"));
}
} // class NullLocationStAXSource
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
More of a hack than an actual solution – see the NullLocationStAXSource class in the provided test case. It doesn't work with the Woodstox StAX provider plugged into the runtime:
```
Event buffer:
StartDocumentEventImpl(location: null)
SimpleStartElement(location: null)
CharactersEventImpl(location: null)
EndElementEventImpl(location: null)
EndDocumentEventImpl(location: null)
Exception in thread "main" javax.xml.transform.TransformerException: javax.xml.transform.TransformerException: java.lang.NullPointerException: Cannot invoke "javax.xml.stream.Location.getSystemId()" because the return value of "javax.xml.stream.events.XMLEvent.getLocation()" is null
...
java.lang.NullPointerException: Cannot invoke "javax.xml.stream.Location.getSystemId()" because the return value of "javax.xml.stream.events.XMLEvent.getLocation()" is null
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.StAXEvent2SAX$1.getSystemId(StAXEvent2SAX.java:286)
at java.xml/com.sun.org.apache.xml.internal.dtm.ref.sax2dtm.SAX2DTM.setDocumentLocator(SAX2DTM.java:1610)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.StAXEvent2SAX.handleStartDocument(StAXEvent2SAX.java:275)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.StAXEvent2SAX.bridge(StAXEvent2SAX.java:160)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.StAXEvent2SAX.parse(StAXEvent2SAX.java:114)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.dom.XSLTCDTMManager.getDTM(XSLTCDTMManager.java:294)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.dom.XSLTCDTMManager.getDTM(XSLTCDTMManager.java:214)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.getDOM(TransformerImpl.java:582)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:783)
at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:395)
at TransformPipelineTest.main(TransformPipelineTest.java:53)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:484)
at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:208)
at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:135)
```
FREQUENCY : always