Question

I have a xsd say request.xsd and corresponding jaxb generated classes. Now I got a xml file request.xml which I am able to unmarshal and create "request" object.

I have many element tags in xml some of which are available multiple times. I need to create an java.util.List which should have all leaf node values.

For example :

Below is my request.xml :

<Request>
  <Operation>manual</Operation>
  <Work>
     <WorkModule>
          <Name>AXN</Name>
     </WorkModule>
  </Work>
  <Identifier>
     <WorkStatus>
          <WorkName>CCH</WorkName>
     </WorkStatus>
     <WorkStatus>
          <WorkName>TMH</WorkName>
     </WorkStatus>
  </Identifier>
</Request>

Below is my JAXB generated Request class. Simillarly there are other classes corresponding to each xml element:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "Operation",
    "Work",
    "Identifier"
})
@XmlRootElement(name = "Request", namespace = "http://www.sprts.com/clm/nso/mahsgd")
public class Request{

    @XmlElement(name = "Operation", required = true)
    protected Operation operation;
    @XmlElement(name = "Work", required = true)
    protected Work work;
    @XmlElement(name = "Identifier", required = true)
    protected Identifier identifier;

    \\ getters and setters
}

So using JAXB I can get unmarshalled Request object having all values in xml file.

Now how do I get all leaf node values (operation, name, workName) in a generic manner without using getters from request object and each of which then I can put in a some collection let say List. I have heard of DOM being used to do similar stuff but I need to use JAXB for the same.

(Without using getters from request object like String opertaion = request.getOperation(); or String name = request.getWork().getWorkModule().getName();)

--EDIT--

Can someone help me out in finding an optimal solution to this. Let me know if problem statement is not clear.

--EDIT-- With Doughan & Alexandros's help and some around could able to achieve the same. Not sure if the work around (converting JAXB object to DOM object to InputSource) is the best solution. Below is the working code.

     JAXBContext jc = JAXBContext.newInstance(JAXBObject.class);

     // Create the Document
     DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
     DocumentBuilder db = dbf.newDocumentBuilder();
     Document document = db.newDocument();

     // Marshal the Object to a Document
     Marshaller marshaller = jc.createMarshaller();
     marshaller.marshal(jaxbObject, document);

    XPathFactory xpf = XPathFactory.newInstance();
    XPath xp = xpf.newXPath();

    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    Source xmlSource = new DOMSource(document);
    Result outputTarget = new StreamResult(outputStream);
    TransformerFactory.newInstance().newTransformer().transform(xmlSource,outputTarget);
    InputStream is = new ByteArrayInputStream(outputStream.toByteArray());
    InputSource source = new InputSource(is);

    NodeList leafNodeObjects = (NodeList) xp.evaluate("//*[not(*)]", source, XPathConstants.NODESET);

    for(int x=0; x<leafNodeObjects.getLength(); x++) {
                    System.out.print("nodeElement = ");
                    System.out.print(leafNodeObjects.item(x).getNodeName());
                    System.out.print(" and node value = ");
                    System.out.println(leafNodeObjects.item(x).getTextContent());
                    inputDtos.add(new InputDto(leafNodeObjects.item(x).getNodeName(),
                            leafNodeObjects.item(x).getTextContent()));
   }
Was it helpful?

Solution

From your bounty comment:

I want to create a list of NodeObject where NodeObject has nodeElement and nodeValue property. exmaple. if I have an element like Anil then I will have one NodeObject for this element with nodeElement = name and nodeValue = property.

You could use the following XPath to get the leaf nodes from any XML document (see: How to select all leaf nodes using XPath expression?):

//*[not(*)]

Here it is in action using the javax.xml.xpath APIs:

import javax.xml.xpath.*;
import org.w3c.dom.*;
import org.xml.sax.InputSource;

public class Demo {

    public static void main(String[] args) throws Exception {
        XPathFactory xpf = XPathFactory.newInstance();
        XPath xp = xpf.newXPath();

        InputSource xml = new InputSource("input.xml");
        NodeList leafNodeObjects = (NodeList) xp.evaluate("//*[not(*)]", xml, XPathConstants.NODESET);

        for(int x=0; x<leafNodeObjects.getLength(); x++) {
            System.out.print("nodeElement = ");
            System.out.print(leafNodeObjects.item(x).getNodeName());
            System.out.print(" and node value = ");
            System.out.println(leafNodeObjects.item(x).getTextContent());
        }
    }

}

Below is the output from running this demo code:

nodeElement = Operation and node value = manual
nodeElement = Name and node value = AXN
nodeElement = WorkName and node value = CCH
nodeElement = WorkName and node value = TMH

OTHER TIPS

My question is: are you able to CHANGE the XSD to suit your needs, or is the XSD controlled by somebody else and you MUST use it as-is?

This is important because of the way JAXB works. Basically, JAXB translates an XSD to Java classes. It can also do the reverse (translate Java classes to XSD). The relationship is described in detail here: http://docs.oracle.com/cd/E21764_01/web.1111/e13758/data_types.htm

In your case, I am assuming that somebody wrote an XSD which you used to generate Java classes, but these classes have a lot of: "Something getSomething1(), Something getSomething2(), ... Something getSomethingN() methods when you would prefer to have a List getListOfSomethings() method.

There are two ways to fix this:

(1) Change the XSD so that the "somethings" are part of a complex type that is a sequence (or anything that would cause JAXB to generate a getter for a list, as per my original answer).

This is not always possible. If the XSD is controlled by some external entity which says "this is what my data looks like, you must live with it or else your application will not be able to read my data" then you cannot do this. To give you a concrete example, suppose your application wants to read EAD data from the US library of congress. The XSD for it is here: http://www.loc.gov/ead/eadschema.html. This XSD is what it is. You cannot change it. If you change it your application will be working with YOUR definition of the data which is different. You must consider approach (2) below as you have no control over the XSD.

(2) Don't use JAXB. Instead use an XML API that allows you to query for elements. This way you can collect all "Somethings" with (for example) an XPath query (see http://docs.oracle.com/javase/tutorial/jaxp/xslt/xpath.html).

You could create a wrapper class that loads the XML and has a List getSomethings() method. This would be along these lines:

public class RequestWrapper {
    Document doc;
    public RequestWrapper(String xmlUri) {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        this.doc = builder.parse(xmlUri);
    }

    public List<Something> getSomethings() {
        XPathFactory xPathfactory = XPathFactory.newInstance();
        XPath xpath = xPathfactory.newXPath();
        XPathExpression expr = xpath.compile(<DEFINE A SUITABLE EXPRESSION>);
        NodeList nl = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);

        List<Something> somethings = new LinkedList<Something>();
        // loop over the nodelist creating instances of Something
        return somethings;
    }
}

Here is a nice tutorial for using XPath with Stax which might come in handy: http://www.vogella.com/articles/JavaXML/article.html

(3) If you are willing to give up on standard Java APIs, you could consider an library that gives you more control over the bindings to Java, like Castor: http://castor.codehaus.org/xml-framework.html

Ultimately, your problem is that either the data is presented in an inconvenient manner, in which case you must do (2) or (3), or YOU have defined an inconvenient XSD, in which case you must do (1).

Are you defining the structure of the XML, or has someone supplied you with a fixed XSD that you MUST use?

If you are actually defining the structure of the request XML, you can use annotations such as @XmlElement and @XmlElementWrapper to have JAXB work with collections. See this for more information: http://blog.bdoughan.com/2010/09/jaxb-collection-properties.html

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top