How to unmarshall SOAP response using JAXB if namespace declaration is on SOAP envelope?
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
Comments
-
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 almost 12 yearsThanks 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 almost 12 yearsHi Blaise! Thanks! I saw your tutorial on blog.bdoughan.com/2010/12/case-insensitive-unmarshalling.html 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 isgetAttributePrefix(int index)
. -
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 almost 12 yearsSorry. 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 almost 12 yearsNamespace 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 almost 12 yearsYup. 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 almost 12 yearsEssentially 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 almost 12 yearsNamespace 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 thehttp://schemas.xmlsoap.org/soap/envelope/
namespace, containing aBody
element in thehttp://schemas.xmlsoap.org/soap/envelope/
namespace, containing agetNumberResponse
element in thehttp://example.com/
namespace. This is all the information the unmarshaller needs, it doesn't care what prefixes were used originally. You pass just thegetNumberResponse
element to JAXB. -
Arci almost 12 yearsYay! Thanks! It's now working! By the way, I changed your code from
XMLInputFactory.newFactory()
toXMLInputFactory.newInstance()
. -
Arci almost 12 yearsThanks 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 almost 12 years@Arci -
XMLInputFactory.newFactory()
is now the preferred API overXMLInputFactory.newInstance()
, see:docs.oracle.com/javase/6/docs/api/javax/xml/stream/…. -
Arci almost 12 years@Blaise Doughan: Sorry. I'm using JDK 1.5. Maybe that's the reason why
newFactory()
does not appear onXMLInputFactory
. Thanks for the note! By the way, how can I get the attribute name ofxsr
? Is there something likexsr.getCurrentTagAttributeName()
? I'm thinking of looping throughxsr
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 almost 12 yearsScratch my last question. I already found the method. I'm calling the wrong method earlier. Thanks again!
-
Ian Roberts almost 12 yearsAt 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 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 almost 12 years@BlaiseDoughan - so would I if I'd thought of it :-)
-
Arci almost 12 years@IanRoberts: Yup, I've already got it working using Blaise's code. Thanks Ian and Blaise!