How to unmarshall SOAP response using JAXB if namespace declaration is on SOAP envelope?

54,393

Solution 1

You can use a StAX parser to parse the SOAP message and then advance the XMLStreamReader to the getNumberResponse element and use the unmarshal method that takes a class parameter. A StAX parser is included in Java SE 6, but you will need to download one for Java SE 5. Woodstox is a popular StAX parser.

Response

package forum11465653;

public class Response {

    private long number;

    public long getNumber() {
        return number;
    }

    public void setNumber(long number) {
        this.number = number;
    }

}

Demo

An XMLStreamReader has a NamespaceContext. The NamespaceContext knows all the active namespace declarations for the current level. Your JAXB (JSR-222) implementation will be able to leverage this to get the necessary information.

package forum11465653;

import java.io.FileReader;
import javax.xml.bind.*;
import javax.xml.stream.*;

public class Demo {

    public static void main(String[] args) throws Exception{
        XMLInputFactory xif = XMLInputFactory.newFactory();
        XMLStreamReader xsr = xif.createXMLStreamReader(new FileReader("src/forum11465653/input.xml"));
        xsr.nextTag(); // Advance to Envelope tag
        xsr.nextTag(); // Advance to Body tag
        xsr.nextTag(); // Advance to getNumberResponse tag
        System.out.println(xsr.getNamespaceContext().getNamespaceURI("ns"));

        JAXBContext jc = JAXBContext.newInstance(Response.class);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        JAXBElement<Response> je = unmarshaller.unmarshal(xsr, Response.class);
        System.out.println(je.getName());
        System.out.println(je.getValue());
    }

}

Output

http://example.com/
{http://example.com/}getNumberResponse
forum11465653.Response@781f6226

Solution 2

You could parse the whole SOAP message using a DOM parser, which would be able to resolve all the namespaces at parse time. Then extract the element you require from the resulting DOM tree and pass that to the unmarshaller.

DomDemo

package forum11465653;

import java.io.File;
import javax.xml.bind.*;
import javax.xml.parsers.*;
import javax.xml.transform.dom.DOMSource;
import org.w3c.dom.*;

public class DomDemo {

    public static void main(String[] args) throws Exception{
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document d = db.parse(new File("src/forum11465653/input.xml"));
        Node getNumberResponseElt = d.getElementsByTagNameNS("http://example.com/", "getNumberResponse").item(0);

        JAXBContext jc = JAXBContext.newInstance(Response.class);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        JAXBElement<Response> je = unmarshaller.unmarshal(new DOMSource(getNumberResponseElt), Response.class);
        System.out.println(je.getName());
        System.out.println(je.getValue());
    }

}

Output

{http://example.com/}getNumberResponse
forum11465653.Response@19632847
Share:
54,393
Arci
Author by

Arci

An Android and J2EE developer.

Updated on October 12, 2020

Comments

  • Arci
    Arci over 3 years

    JAXB can only unmarshall an XML if the soap envelope has already been removed. However, the SOAP response I'm trying to unmarshall has its namespace declaration on the soap envelope. If I remove the soap envelope, the namespace declaration will also be removed. As such the prefix of the tags will be referring to none. This causes JAXB to throw an error. How can I unmarshall a SOAP response using JAXB if the namespace is declared on the SOAP envelope?

    A similar sample of the XML that I need to unmarshall is below:

    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://example.com/">
        <soapenv:Body>
            <ns:getNumberResponse>
                <number>123456789</number>
            </ns:getNumberResponse>
        </soapenv:Body>
    </soapenv:Envelope>
    

    This is what happens if I remove the soap envelope:

    <ns:getNumberResponse>
        <number>123456789</number>
    </ns:getNumberResponse>
    

    If I removed the soap envelope, the namespace declaration will also be gone. JAXB won't be able to unmarshall the above xml because the namespace definition for the prefix "ns" is missing. As such it will return a "The prefix "ns" for element "ns:getNumberResponse" is not bound." error message. How do I fix it?

    Please note that I'm using JDK 1.5. I'm not sure if there is a way to solve this in JDK 1.6. I was able to use JAXB because I downloaded the required jars for using JAXB.

  • Arci
    Arci almost 12 years
    Thanks Ian! I think this will only work if only the first tag element is using a namespace prefix. If there will be a lot of inner tags with namespace prefix, then there will also be a need to use getElementsByTagName for each of those tags. This will defeat the purpose of the unmarshaller.
  • Arci
    Arci almost 12 years
    Hi Blaise! Thanks! I saw your tutorial on blog.bdoughan.com/2010/12/case-insensitive-unmarshalling.htm‌​l and tried to modify your code a bit to replace the prefix instead of the attributes but I'm getting a Message: could not determine namespace bound to element prefix ns error message. The method that I override is getAttributePrefix(int index).
  • bdoughan
    bdoughan almost 12 years
    @Arci - You won't need to override anything. Since you are parsing the entire document it will contain all the necessary namespace information.
  • Arci
    Arci almost 12 years
    Sorry. I got confused there. I thought StreamReaderDelegate is like an interceptor which is called before the XML is unmarshalled? Based from your example, you changed the local attribute name and the attribute name to lower case so it will always match each other when you unmarshall it. So I thought I must do the same with the prefix? I won't be able to unmarshall it if a namespace prefix is found on the xml without its matching namespace declaration so I'm planning to remove all the prefixes completely. Can you please enlightened me more on this matter?
  • Ian Roberts
    Ian Roberts almost 12 years
    Namespace prefixes in XML are a syntactic trick to associate the right namespace URI with the right element. The only thing that matters to downstream components like the JAXB unmarshaller is the namespace URI and the local name (the bit after the colon) - once you have a DOM tree all the elements at every level have their namespaces fully resolved and the prefix mappings are no longer relevant.
  • Arci
    Arci almost 12 years
    Yup. I know that. However, JAXB cannot unmarshall with the soap enevelope still present. The problem is the namespace is declared on the soap envelope tag. If I remove the envelope, the namespace definition will be also be gone. JAXB can only function properly if the appropriate namespace definition is also present.
  • Ian Roberts
    Ian Roberts almost 12 years
    Essentially Blaise and I are both suggesting the same solution but using different XML APIs - parse the complete envelope document with an XML parsing tool that has all the namespace information available to it, but pass only the bit of the XML tree that you're interested in to the JAXB unmarshaller.
  • Ian Roberts
    Ian Roberts almost 12 years
    Namespace prefix mappings only matter to the original parser (DOM, StAX, whatever). You parse the whole envelope and end up with a tree structure where the root element is an Envelope element in the http://schemas.xmlsoap.org/soap/envelope/ namespace, containing a Body element in the http://schemas.xmlsoap.org/soap/envelope/ namespace, containing a getNumberResponse element in the http://example.com/ namespace. This is all the information the unmarshaller needs, it doesn't care what prefixes were used originally. You pass just the getNumberResponse element to JAXB.
  • Arci
    Arci almost 12 years
    Yay! Thanks! It's now working! By the way, I changed your code from XMLInputFactory.newFactory() to XMLInputFactory.newInstance().
  • Arci
    Arci almost 12 years
    Thanks again for your reply! However, if getNumberResponse has a prefix and you unmarshall it using JAXB, JAXB will throw an exception if it can't find the namespace definition of the prefix.
  • bdoughan
    bdoughan almost 12 years
    @Arci - XMLInputFactory.newFactory() is now the preferred API over XMLInputFactory.newInstance(), see:docs.oracle.com/javase/6/docs/api/javax/xml/stream/….
  • Arci
    Arci almost 12 years
    @Blaise Doughan: Sorry. I'm using JDK 1.5. Maybe that's the reason why newFactory() does not appear on XMLInputFactory. Thanks for the note! By the way, how can I get the attribute name of xsr? Is there something like xsr.getCurrentTagAttributeName()? I'm thinking of looping through xsr because some of the response contains a soap envelope. And then I will check if I'm already on the right tag by comparing the current attribute name to the expected attribute name.
  • Arci
    Arci almost 12 years
    Scratch my last question. I already found the method. I'm calling the wrong method earlier. Thanks again!
  • Ian Roberts
    Ian Roberts almost 12 years
    At the risk of sounding like a stuck record, no, it won't. If it's unmarshalling from a DOM Node then it doesn't care what the prefixes were. Prefix mappings only matter if it's unmarshalling directly from the original XML. Anyway, it sounds like you've got things working using StAX.
  • bdoughan
    bdoughan almost 12 years
    +1 - This would definitely work. I have expanded your demo code into a complete working example. I still prefer the StAX approach that avoids the need of brining the entire document into memory before passing it to JAXB: stackoverflow.com/a/11467459/383861
  • Ian Roberts
    Ian Roberts almost 12 years
    @BlaiseDoughan - so would I if I'd thought of it :-)
  • Arci
    Arci almost 12 years
    @IanRoberts: Yup, I've already got it working using Blaise's code. Thanks Ian and Blaise!