Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the simplest way to extract an XML node for JAXB.unmarshal()?

I use the wsdl2java goal of cxf-codegen-plugin to generate Java from a WSDL. Then, in my tests, I use JAXB.unmarshal() to populate classes from a raw webservice XML result.

A typical example is GetAllResponseType response = unmarshal("get-all.xml", GetAllResponseType.class), using the following method:

<T> T unmarshal(String filename, Class<T> clazz) throws Exception {
    InputStream body = getClass().getResourceAsStream(filename);
    return javax.xml.bind.JAXB.unmarshal(body, clazz);
}

The problem is this: The raw XML response always have enclosing Envelope and Body tags which are not generated as classes by wsdl2java:

<n4:Envelope xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:n="http://www.informatica.com/wsdl/"
         xmlns:n4="http://schemas.xmlsoap.org/soap/envelope/" xmlns:n5="http://schemas.xmlsoap.org/wsdl/"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <n4:Body>
    <n:getAllResponse xmlns:n="http://www.informatica.com/wsdl/">
        <n:getAllResponseElement>
           ...
        </n:getAllResponseElement>
    </n:getAllResponse>
  </n4:Body>
</n4:Envelope>

So, in order to use JAXB.unmarshal() I have to

  1. either strip away the surrounding Envelope/Body tags manually in get-all.xml
  2. or extract the getAllResponse node and re-convert it to an InputStream
  3. or create the Envelope and Body classes

Currently I do 2, but it's a lot of code:

<T> T unmarshal(String filename, Class<T> clazz) throws Exception {
    InputStream is = getClass().getResourceAsStream(filename);
    InputStream body = nodeContent(is, "n4:Body");
    return javax.xml.bind.JAXB.unmarshal(body, clazz);
}

InputStream nodeContent(InputStream is, String name) throws Exception {
    DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
    DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
    Document doc = docBuilder.parse(is);
    Node node = firstNonTextNode(doc.getElementsByTagName(name).item(0).getChildNodes());
    return nodeToStream(node);
}

Node firstNonTextNode(NodeList nl) {
    for (int i = 0; i < nl.getLength(); i++) {
        if (!(nl.item(i) instanceof Text)) {
            return nl.item(i);
        }
    }
    throw new RuntimeException("Couldn't find nontext node");
}

InputStream nodeToStream(Node node) throws Exception {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    Source xmlSource = new DOMSource(node);
    Result outputTarget = new StreamResult(outputStream);
    TransformerFactory.newInstance().newTransformer().transform(xmlSource, outputTarget);
    return new ByteArrayInputStream(outputStream.toByteArray());
}

My questions are:

  • Is there an easier way to the extraction in 2? I am tempted to just do a regexp. I tried XPath, but somehow I couldn't get it to work. Code examples would be helpful.
  • Can I get wsdl2java to create the Body / Envelope classes (3), or is it easy to create them myself?
like image 552
neu242 Avatar asked Jun 10 '13 07:06

neu242


People also ask

How do you Unmarshal XML?

To unmarshal an xml string into a JAXB object, you will need to create an Unmarshaller from the JAXBContext, then call the unmarshal() method with a source/reader and the expected root object.

What is the JAXB process for converting an XML document to Java object called?

Unmarshalling is the process of reading XML documents into Java objects. The JAXBContext class provides the client's entry point to the JAXB API. It provides API for marshalling, unmarshalling and validating.

How does JAXB read XML?

To read XML, first get the JAXBContext . It is entry point to the JAXB API and provides methods to unmarshal, marshal and validate operations. Now get the Unmarshaller instance from JAXBContext . It's unmarshal() method unmarshal XML data from the specified XML and return the resulting content tree.


2 Answers

Use the DOMSource to pass a Node as input. The following method takes a org.w3c.dom.Node as input and returns the unmarshalled class.

private <T> T unmarshal(Node node, Class<T> clazz) throws JAXBException {
        XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
        Source xmlSource = new DOMSource(node);
        Unmarshaller unmarshaller = JAXBContext.newInstance(clazz).createUnmarshaller();
        return unmarshaller.unmarshal(xmlSource, clazz).getValue();
}
like image 98
tkolleh Avatar answered Sep 30 '22 07:09

tkolleh


The node within the n4:Body node can be unmarshalled by utilizing XMLStreamReader and the "raw" JAXB Unmarshaller:

<T> T unmarshal(String filename, Class<T> clazz) throws Exception {
    XMLInputFactory xif = XMLInputFactory.newFactory();
    XMLStreamReader xsr = xif.createXMLStreamReader(getClass().getResourceAsStream(filename));
    xsr.nextTag();
    while (!xsr.getLocalName().equals("Body")) {
        xsr.nextTag();
    }
    xsr.nextTag();
    Unmarshaller unmarshaller = JAXBContext.newInstance(clazz).createUnmarshaller();
    return unmarshaller.unmarshal(xsr, clazz).getValue();
}

Thanks to Blaise Doughan for helping on this answer.

like image 33
neu242 Avatar answered Sep 30 '22 08:09

neu242