1

I am calling a WCF .Net service from a Java proxy. The result is an xml string (in a stream) with inline schema. I would like to unmarshall the values in the xml to a Java class using JAXB. The xml string appears as follows:

<NewDataSet>
  <xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:Locale="">
      <xs:complexType>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
          <xs:element name="Table">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="CustomerID" type="xs:int" minOccurs="0" />
                <xs:element name="CustomerNme" type="xs:string" minOccurs="0" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:choice>
      </xs:complexType>
    </xs:element>
  </xs:schema>
  <Table>
    <CustomerID>1111</CustomerID>
    <CustomerNme>Customer One</CustomerNme>
  </Table>
  <Table>
    <CustomerID>2222</CustomerID>
    <CustomerNme>Customer Two</CustomerNme>
  </Table>
</NewDataSet>

As the basic structure will remain the same my initial thoughts are to:

  1. Use the schema to manually create JAXB Java classes using xjc.
  2. During processing, parse the xml string with embedded schema and throw away the schema and root node NewDataSet.
  3. Use JAXB to unmarshall the string to a java object.

Is there a better way to do this?

UPDATE

Trying @Doughans approach. Close but no cigar yet. A complication is the the xml comes gzipped. So, code below is where I've got to. I've set the Customer class members to upper case to match the exact xml elements but all I get is null for each instance of the unmarshal operation.

//everything good to here...
JAXBElement<byte[]> byteRet = searchCustResponse.getSearchCustomersResult();
byte[] gzBytes = byteRet.getValue(); //these are gzipped bytes

//decompress...
ByteArrayInputStream bais = new ByteArrayInputStream(gzBytes);
GZIPInputStream gzipis = new GZIPInputStream(bais);
//InputStreamReader isr = new InputStreamReader(gzipis, "UTF-8");

//convert xml to string...
StringWriter writer = new StringWriter();
IOUtils.copy(gzipis, writer, "UTF-8");
String xmlString = writer.toString();

//read the string...
//StreamSource xml = new StreamSource(xmlString);
Reader reader = new StringReader(xmlString);

XMLInputFactory xif = XMLInputFactory.newFactory();
XMLStreamReader xsr = xif.createXMLStreamReader(reader);

List<WPTSCustomer> wptsCustomers = new ArrayList<WPTSCustomer>();

try {
    while (xsr.hasNext()) {
        int next = xsr.next();
        //xsr.nextTag();
        if (next == XMLStreamReader.START_ELEMENT) {
            if (xsr.getLocalName().equals("Table")) {
                JAXBContext jc = JAXBContext.newInstance(WPTSCustomer.class);
                Unmarshaller unmarshaller = jc.createUnmarshaller();
                JAXBElement<WPTSCustomer> jb = unmarshaller.unmarshal(xsr, WPTSCustomer.class);
                WPTSCustomer customer = jb.getValue();
                System.out.println(customer.getCustomerID()); //prints null
                System.out.println(customer.getCustomerNme()); //prints null
                wptsCustomers.add(customer);
            }
        }
    }
} finally {

The use of StreamSource did not work for me. Finally I just walked the xml string and that is working up to the point it does the unmarshal which is not populating the object.

UPDATE2

Giving up on JAXB for now. The following code works nicely and I'll just throw the values into my POJO. I did try creating an XSD in JDev from the full xml string returned from the service, then a JAXB class from the XSD. That almost worked except for name conflicts coming from the inline schema. There's probably a way to get around that.

int eventType = xsr.getEventType();
while (xsr.hasNext()) {
    eventType = xsr.next();
    if (xsr.isStartElement() && xsr.getLocalName().equals("Table")) {
        xsr.nextTag();
        while (xsr.hasNext()) {
            if (xsr.isStartElement()) {
                if (xsr.getLocalName().equals("CustomerID")) {
                    System.out.println("id: " + xsr.getElementText());
                }
                if (xsr.getLocalName().equals("CustomerNme")) {
                    System.out.println("name: " + xsr.getElementText());
                }
            }
            xsr.next();
        }
    }

--

2 Answers 2

2

I would parse the entire XML document with StAX, then navigate the XMLStreamReader down to the Table element and use JAXB to unmarshal that.

For More Information

I have written more about this use case on my blog:

Sign up to request clarification or add additional context in comments.

Comments

1

To people who might come to this issue again , If you try to read data from xml string it might not work but if you can get data in InputStream you can pass it to XMLStreamReader It works fine. Great blog by @Blaise.

I used xjc to pick out the schema manually created the Javaobject and then used XMLStreamReader to parse the jax-rs reponse and get the Input Stream. Will try to remove the manual step and update post if successful.

XMLInputFactory xif = XMLInputFactory.newFactory();
StreamSource xml = new StreamSource(httpResponse.getEntity().getContent());
XMLStreamReader xsr = xif.createXMLStreamReader(xml);
xsr.nextTag();
while (!xsr.getLocalName().equals("GetAccountInfoResults")) {
    xsr.nextTag();
}
JAXBContext jc = JAXBContext.newInstance(GetAccountInfoResults.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
JAXBElement<GetAccountInfoResults> jb = unmarshaller.unmarshal(xsr, GetAccountInfoResults.class);
xsr.close();

GetAccountInfoResults result = jb.getValue();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.