Project

Profile

Help

Bug #6387

closed

non-qualified elements declare illegal prefix-emptyNamespace mapping

Added by Grzegorz Grzybek 18 days ago. Updated 6 days ago.

Status:
Resolved
Priority:
Normal
Assignee:
-
Category:
JAXP Java API
Sprint/Milestone:
Start date:
2024-04-11
Due date:
% Done:

0%

Estimated time:
Legacy ID:
Applies to branch:
12, trunk
Fix Committed on Branch:
12, trunk
Fixed in Maintenance Release:
Platforms:
Java

Description

I have a Camel-XQuery application which processes SOAP messages using CXF and Undertow transport.

Roughly the stack trace looks like this:

org.apache.cxf.interceptor.Fault: Could not generate the XML stream caused by: javax.xml.stream.XMLStreamException: Non-default namespace can not map to empty URI (as per Namespace 1.0 # 2) in XML 1.0 documents.
	at org.apache.cxf.databinding.source.XMLStreamDataWriter.write(XMLStreamDataWriter.java:130) ~[cxf-core-4.0.2.jar:4.0.2]
	at org.apache.cxf.databinding.source.XMLStreamDataWriter.write(XMLStreamDataWriter.java:67) ~[cxf-core-4.0.2.jar:4.0.2]
	at org.apache.camel.component.cxf.jaxws.HybridSourceDataBinding$1.write(HybridSourceDataBinding.java:73) ~[camel-cxf-soap-4.0.0.jar:4.0.0]
	...
	at org.apache.cxf.transport.http_undertow.CxfUndertowServlet.invoke(CxfUndertowServlet.java:51) ~[cxf-rt-transports-http-undertow-4.0.2.jar:4.0.2]
	at org.apache.cxf.transport.servlet.AbstractHTTPServlet.handleRequest(AbstractHTTPServlet.java:303) ~[cxf-rt-transports-http-4.0.2.jar:4.0.2]
	...
	at org.apache.cxf.transport.servlet.AbstractHTTPServlet.service(AbstractHTTPServlet.java:278) ~[cxf-rt-transports-http-4.0.2.jar:4.0.2]
	at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74) ~[undertow-servlet-2.3.10.jar:2.3.10]
	...
	at io.undertow.servlet.spec.AsyncContextImpl$TaskDispatchRunnable.run(AsyncContextImpl.java:553) ~[undertow-servlet-2.3.10.jar:2.3.10]
	at org.jboss.threads.ContextHandler$1.runWith(ContextHandler.java:18) ~[jboss-threads-3.5.0.Final.jar:3.5.0.Final]
	at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513) ~[jboss-threads-3.5.0.Final.jar:3.5.0.Final]
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1512) ~[jboss-threads-3.5.0.Final.jar:3.5.0.Final]
	at org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282) ~[xnio-api-3.8.8.Final.jar:3.8.8.Final]
	at java.base/java.lang.Thread.run(Thread.java:842) ~[na:na]
Caused by: javax.xml.stream.XMLStreamException: Non-default namespace can not map to empty URI (as per Namespace 1.0 # 2) in XML 1.0 documents
	at com.ctc.wstx.sw.BaseStreamWriter.throwOutputError(BaseStreamWriter.java:1596) ~[woodstox-core-6.5.1.jar:6.5.1]
	at com.ctc.wstx.sw.SimpleNsStreamWriter.writeNamespace(SimpleNsStreamWriter.java:135) ~[woodstox-core-6.5.1.jar:6.5.1]
	at org.apache.cxf.staxutils.StaxUtils.writeStartElement(StaxUtils.java:835) ~[cxf-core-4.0.2.jar:4.0.2]
	...
	at org.apache.cxf.databinding.source.XMLStreamDataWriter.write(XMLStreamDataWriter.java:126) ~[cxf-core-4.0.2.jar:4.0.2]
	... 41 common frames omitted

This stack trace shows a problem where CXF/StAX tries to process an element without namespace where its parent element is namespaced.

After digging the problem more, I tracked down how the problematic DOM is created and the stack trace is:

"XNIO-3 task-3@16475" prio=5 tid=0x59 nid=NA runnable
  java.lang.Thread.State: RUNNABLE
	  at net.sf.saxon.dom.DOMWriter.startElement(DOMWriter.java:164)
	  at net.sf.saxon.event.TreeReceiver.startElement(TreeReceiver.java:148)
	  at net.sf.saxon.event.ComplexContentOutputter.startContent(ComplexContentOutputter.java:782)
	  at net.sf.saxon.event.ComplexContentOutputter.characters(ComplexContentOutputter.java:272)
	  at net.sf.saxon.event.ComplexContentOutputter.decompose(ComplexContentOutputter.java:846)
	  at net.sf.saxon.event.ComplexContentOutputter.append(ComplexContentOutputter.java:661)
	  at net.sf.saxon.event.Outputter.append(Outputter.java:341)
	  at net.sf.saxon.expr.elab.PullElaborator.lambda$elaborateForPush$0(PullElaborator.java:40)
	  at net.sf.saxon.expr.elab.PullElaborator$$Lambda$1611/0x00007f317c9a8450.processLeavingTail(Unknown Source:-1)
	  at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator.lambda$elaborateForPush$0(FixedElement.java:653)
	  at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator$$Lambda$1612/0x00007f317c9a8678.processLeavingTail(Unknown Source:-1)
	  at net.sf.saxon.expr.instruct.Block$BlockElaborator.lambda$elaborateForPush$4(Block.java:898)
	  at net.sf.saxon.expr.instruct.Block$BlockElaborator$$Lambda$1613/0x00007f317c9a88a0.processLeavingTail(Unknown Source:-1)
	  at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator.lambda$elaborateForPush$0(FixedElement.java:653)
	  at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator$$Lambda$1612/0x00007f317c9a8678.processLeavingTail(Unknown Source:-1)
	  at net.sf.saxon.expr.instruct.Block$BlockElaborator.lambda$elaborateForPush$2(Block.java:864)
	  at net.sf.saxon.expr.instruct.Block$BlockElaborator$$Lambda$1615/0x00007f317c9a8cf0.processLeavingTail(Unknown Source:-1)
	  at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator.lambda$elaborateForPush$0(FixedElement.java:653)
	  at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator$$Lambda$1612/0x00007f317c9a8678.processLeavingTail(Unknown Source:-1)
	  at net.sf.saxon.expr.instruct.Block$BlockElaborator.lambda$elaborateForPush$1(Block.java:854)
	  at net.sf.saxon.expr.instruct.Block$BlockElaborator$$Lambda$1616/0x00007f317c9a8f18.processLeavingTail(Unknown Source:-1)
	  at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator.lambda$elaborateForPush$0(FixedElement.java:653)
	  at net.sf.saxon.expr.instruct.FixedElement$FixedElementElaborator$$Lambda$1612/0x00007f317c9a8678.processLeavingTail(Unknown Source:-1)
	  at net.sf.saxon.expr.instruct.UserFunction.process(UserFunction.java:712)
	  at net.sf.saxon.expr.UserFunctionCall$UserFunctionCallElaborator.lambda$elaborateForPush$5(UserFunctionCall.java:846)
	  at net.sf.saxon.expr.UserFunctionCall$UserFunctionCallElaborator$$Lambda$1607/0x00007f317c9a1788.processLeavingTail(Unknown Source:-1)
	  at net.sf.saxon.query.XQueryExpression.processQuery(XQueryExpression.java:499)
	  at net.sf.saxon.query.XQueryExpression.run(XQueryExpression.java:473)
	  at org.apache.camel.component.xquery.XQueryBuilder.evaluateAsDOM(XQueryBuilder.java:234)
	  at org.apache.camel.component.xquery.XQueryBuilder.evaluate(XQueryBuilder.java:194)
	  at org.apache.camel.component.xquery.XQueryBuilder.evaluate(XQueryBuilder.java:169)
	  ...
	  at org.apache.camel.support.processor.DelegateSyncProcessor.process(DelegateSyncProcessor.java:65)
	  at org.apache.camel.impl.engine.CamelInternalProcessor.process(CamelInternalProcessor.java:383)
	  ...
	  at org.apache.camel.component.cxf.jaxws.CxfConsumer$CxfConsumerInvoker.invoke(CxfConsumer.java:162)
	  at org.apache.cxf.interceptor.ServiceInvokerInterceptor$1.run(ServiceInvokerInterceptor.java:59)
	  at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
	  ...
	  at org.apache.cxf.transport.http_undertow.UndertowHTTPHandler.handleRequest(UndertowHTTPHandler.java:122)
	  at io.undertow.server.Connectors.executeRootHandler(Connectors.java:393)
	  at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:859)
	  ...
	  at org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282)
	  at java.lang.Thread.run(Thread.java:842)

The document being processed looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<msg:soap-message xmlns:msg="http://data.example.com">
    <header:header-container xmlns:header="http://header.example.com">
        <header:header1>
            <id>12</id>
            <system-id>33</system-id>
            <time />

According to XSD, msg:soap-message, header:header-container and header:header1 are qualified elements, but id, system-id and time are defined like this:

<xsd:complexType name="header1">
    <xsd:sequence>
        <xsd:element name="id" nillable="true" type="xsd:string">
        </xsd:element>
        <xsd:element name="system-id" nillable="true" type="xsd:string">
        </xsd:element>
...

because id and system-id don't have form attribute and there's no explicit elementFormDefault="qualified", these elements effective are in no namespace at all.

When taking this document literally and parsing it using standard JAXP DOM, I can confirm it by printing QNames and namespaces:

<?xml version="1.0" encoding="UTF-8"?>
<msg:soap-message> (msg:http://data.example.com)
  <header:header-container> (header:http://header.example.com)
    <header:header1> (header:http://header.example.com)
      <id> (null:null)
      <system-id> (null:null)
      <time> (null:null)
...

When changing this document to:

<?xml version="1.0" encoding="UTF-8"?>
<msg:soap-message> (msg:http://data.example.com)
  <header:header-container> (header:http://header.example.com)
    <header:header1> (header:http://header.example.com)
      <id xmlns:header=""> (null:null)
      <system-id> (null:null)
      <time> (null:null)
...

I'm getting:

[Fatal Error] :5:32: The value of the attribute "prefix="xmlns",localpart="header",rawname="xmlns:header"" is invalid. Prefixed namespace bindings may not be empty.

org.xml.sax.SAXParseException; lineNumber: 5; columnNumber: 32; The value of the attribute "prefix="xmlns",localpart="header",rawname="xmlns:header"" is invalid. Prefixed namespace bindings may not be empty.

	at java.xml/com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:261)
	at java.xml/com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:339)
	at java.xml/javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:122)
...

And this is exactly what Saxon-HE 12.4 is doing in net.sf.saxon.dom.DOMWriter#startElement():

  • elemName parameter is "id"
  • attributes parameter is net.sf.saxon.om.EmptyAttributeMap
  • namespaces parameter is:
namespaces = {net.sf.saxon.om.NamespaceMap@12206} ""
 prefixes: java.lang.String[]  = {java.lang.String[0]@16626} []
 uris: net.sf.saxon.om.NamespaceUri[]  = {net.sf.saxon.om.NamespaceUri[0]@16627} 

nsStack.peek() passed to namespaces.getDifferences() is:

parentNamespaces = {net.sf.saxon.om.NamespaceMap@16534} "header=http://header.example.com "
 prefixes: java.lang.String[]  = {java.lang.String[1]@16633} ["header"]
 uris: net.sf.saxon.om.NamespaceUri[]  = {net.sf.saxon.om.NamespaceUri[1]@16634} 
  0 = {net.sf.saxon.om.NamespaceUri@16638} "http://header.example.com"

While parentNamespaces is fine (matches NS declarations of parent "header:header1" element, the result of namespaces.getDifferences() is:

declarations = {net.sf.saxon.om.NamespaceBinding[1]@16546} 
 0 = {net.sf.saxon.om.NamespaceBinding@16642} "header="
  prefix: java.lang.String  = {@16635} "header"
  uri: net.sf.saxon.om.NamespaceUri  = {net.sf.saxon.om.NamespaceUri@16644} ""

It means that in element "id", org.w3c.dom.Element#setAttributeNS() is called with:

  • namespace: http://www.w3.org/2000/xmlns/
  • qname: xmlns:header
  • nsURI (value): "" (empty string)

In my opinion, net.sf.saxon.om.NamespaceMap#getDifferences() should not simply do:

} else if (j < other.prefixes.length) {
    // prefix present in other map, absent from this: add an undeclaration
    result.add(new NamespaceBinding(other.prefixes[j], NamespaceUri.NULL));

it CAN'T add NULL namespace URI when prefix is not empty.

Sorry for not providing full reproducer, but I hope my analysis is enough.

kind regards Grzegorz Grzybek

Actions #1

Updated by Grzegorz Grzybek 18 days ago

Line numbers should match Saxon-HE 12.0 (because I was checking if earlier versions work).

Actions #2

Updated by Michael Kay 18 days ago

Thanks for reporting this and for your thorough analysis.

There's essentially a problem here in that some parts of the system (in particular those using native XDM) implement the XML Namespaces 1.1 model where a namespace can be undeclared, and other parts (using external models) only implement the 1.0 model (where it can't). There's a need to bridge between the two models, and it looks like on this path we are failing to do this. Perhaps the NamespaceDifferencer needs to recognise the two cases and treat them differently.

Actions #3

Updated by Grzegorz Grzybek 18 days ago

I can also say that previously we were using Camel 2.23.x which was using Saxon-HE 9.8.0-14, where net.sf.saxon.dom.DOMWriter#startElement() was not doing any namespace difference calculations.

Actions #4

Updated by Michael Kay 18 days ago

There was a major redesign in the handling of namespaces in Saxon 10, to conform more closely with the way they are modelled in XDM. This led to a number of problems when interfacing with non-XDM tree representations. I thought we had now cleared these, but it seems not.

Actions #5

Updated by Michael Kay 18 days ago

I think the problem here is that the DOM itself does accept XML 1.1 style namespace undeclarations (in fact, DOM accepts almost anything, it really doesn't care about namespace consistency at all), but a particular consumer of the DOM may have expectations about what it finds.

Perhaps we should therefore behave differently depending on whether XML 1.1 is enabled as a Saxon configuration property.

Of course, a DOM consumer that can't handle this is also going to fail on many programmatically-constructed DOMs.

Actions #6

Updated by Michael Kay 18 days ago

I suspect that the code fragment highlighted:

          } else if (j < other.prefixes.length) {
                // prefix present in other map, absent from this: add an undeclaration
                result.add(new NamespaceBinding(other.prefixes[j], NamespaceUri.NULL));
                j++;

should be

          } else if (j < other.prefixes.length) {
                // prefix present in other map, absent from this: add an undeclaration
                if (addUndeclarations) {
                    result.add(new NamespaceBinding(other.prefixes[j], NamespaceUri.NULL));
                }
                j++;

but I need to check it very carefully to be sure.

Actions #7

Updated by Michael Kay 6 days ago

Yes, looking at the code more carefully, there are two branches dealing with the case of a prefix that is present in the "other" namespace map, and not in this one, and the logic in both branches should be the same: only add an undeclaration if the addUndeclarations flag is set (which it usually isn't). The only difference between the two paths is that the one that needs fixing is taken in the case where the prefix in question is alphabetically greater than any prefix in the other map.

Actions #8

Updated by Michael Kay 6 days ago

  • Status changed from New to Resolved
  • Applies to branch trunk added
  • Fix Committed on Branch 12, trunk added

Please register to edit this issue

Also available in: Atom PDF