Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider using the namespace prefix available in the qualified name before trying to resolve it from the namespace definitions #2069

Open
BreezeBlocksTool opened this issue Feb 22, 2024 · 0 comments

Comments

@BreezeBlocksTool
Copy link

Describe the bug

We just switched to using com.sun.xml.ws:jaxws-ri:4.0.2 which uses org.eclipse.persistence:org.eclipse.persistence.core:4.0.2, instead of using com.sun.xml.bind:jaxb-impl:2.3.4

We try to unmarshal an object with a List of Object child (example below), so the unmarshalling creates an ElementNSImpl to create the inner Objects.
These inner objects have a different namespace compared to the rest of the test file
In the previous implementation we used, the "name" field of the ElementNSImpl was the fully qualified name (ns:nameOfField) whereas now, it's just the same as the localName field (nameOfField without the "ns:" part)

I got quite deep into debugguing and it turns out that we only unmarshal an inner node of the source file and the namespaces are defined on the higher level. (see test case below)

In the SAXFragmentBuilder class, the prefix is fetched afterwards using the owning record

    int qNameColonIndex = qName.indexOf(Constants.COLON);
    if ((namespaceURI != null) && (qNameColonIndex == -1)) {
        //check for a prefix from the unmarshal record:
        String prefix = owningRecord.resolveNamespaceUri(namespaceURI);
        if (prefix != null && prefix.length() >0){
            qName = prefix + Constants.COLON + qName;
            qNameColonIndex = prefix.length();
        }
    }

but in our case, the prefix cannot be resolved since the namespaceMap used in StackUnmarshalNamespaceResolver is empty (since we did not unmarshal the part were the namespaces were defined)

but it feels akward to fetch the prefix from the map when it already is given on the node

My feeling is that the solution lies in the UnmarshalRecordImpl class, within the updateXPathFragment method :

         if (namespaceURI != null && namespaceURI.length() == 0) {
             this.xPathFragment.setLocalName(qName);
             this.xPathFragment.setNamespaceURI((String)null);
         } else {
             this.xPathFragment.setLocalName(localName);
             this.xPathFragment.setNamespaceURI(namespaceURI);
         }
     }

In our case, we fall in the "else" part of the code since we have a namespaceURI that's not null and not blank, so the localName is set to localName and the namespaceURI to namespaceURI (which is normal) but it feels like we could use the prefix part of the xpathFragment :
this.xPathFragment.setPrefix(extractPrefix(qName));

The code would then use the prefix set in the xPathFragment rather than resolving it :
XMLAnyCollectionMappingNodeValue::startElement adds the prefix to the name when it was set on the xPathFragment, which is exactly what we expect in our case

    String qnameString = xPathFragment.getLocalName();
    if (xPathFragment.getPrefix() != null) {
        String var10000 = xPathFragment.getPrefix();
        qnameString = var10000 + ":" + qnameString;
    }

    handler.startElement(xPathFragment.getNamespaceURI(), xPathFragment.getLocalName(), qnameString, atts);

To Reproduce
Using Java 17 and org.eclipse.persistence:org.eclipse.persistence.core:4.0.2 (but previous versions seem to have the same issue)

Example test :

    @Test
    void testReproduceBug() throws Exception {
        File file = new File(getClass().getClassLoader().getResource("files/testBug.xml").getFile());
        JAXBContext jaxbContext = JAXBContext.newInstance(MyParentObject.class);
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        SOAPMessage message = MessageFactory.newInstance(SOAP_1_1_PROTOCOL)
                .createMessage(null, new ByteArrayInputStream(Files.readAllBytes(file.toPath())));
        MyParentObject value = unmarshaller.unmarshal(message.getSOAPBody().extractContentAsDocument(), MyParentObject.class).getValue();

        assertThat(((ElementNSImpl)value.getAny().get(0)).getNodeName()).isEqualTo("myns:myChildObject");
    }

MyParentObject class :

    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "myParentObject")
    public static class MyParentObject {

        @XmlAnyElement(lax = true)
        protected List<Object> any;

        public List<Object> getAny() {
            if (any == null) {
                any = new ArrayList<>();
            }
            return this.any;
        }

    }

test file (files/testBug.xml) :

<soapenv:Envelope
        xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
        xmlns:api="http://myApi.fr/api"
        xmlns:myns="http://myNameSpaceUri.com/xsd">
    <soapenv:Header>
        <api:timestamp>2019-10-16T00:00:00.000Z</api:timestamp>
        <api:version>1.0</api:version>
    </soapenv:Header>
    <soapenv:Body>
            <api:myParentObject>
                <myns:myChildObject>
                    <myGrandChildOject testAttribute="test"/>
                </myns:myChildObject>
            </api:myParentObject>
    </soapenv:Body>
</soapenv:Envelope>

Expected behavior
a fully qualified "name" field with the original namespace qualifier in the child ElementNsImpl object

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant