2

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()));
   }
Suvasis
  • 1,451
  • 4
  • 24
  • 42
  • 1
    You have wonderful objects at your hand where every attribute / method has some meaning. What are you trying to achieve that you want a generic way and contemplate something like DOM to replace your typed data structure? However, the obvious but bad answer would be "use reflection". But I urge you to explain what you want to achieve so we can suggest some better solution. – Matthias Dec 20 '13 at 13:15
  • Using simple getters from request object I could create a list which works fine. I have some 150 fields in xml so manually mapping each xml element is still ok but I am expecting this number to grow in future. In that case again I have to verify fields added or removed from xml and corresponding mapping has to be done to put in collection which I want to avoid. – Suvasis Dec 20 '13 at 13:39

3 Answers3

2

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
Community
  • 1
  • 1
bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • Your code works fine to get the leaf node values but I dont have access to the xml file. I get an unmarshalled pojo object to work with. Is there any way I can use DOM over the pojo instead of xml file? – Suvasis Jan 03 '14 at 16:03
  • @Suvasis - You could try issuing the XPath on an instance of `JAXBSource` or you could marshal the JAXB objects to a DOM and issue the XPath on that. – bdoughan Jan 03 '14 at 16:13
  • @Doughan - I was looking for how to convert JAXB object to an InputSource sothat I can apply XPATH through evaluate method to get the leaf node the way you have done above. Could you site any link or hint to refer. – Suvasis Jan 03 '14 at 17:04
  • @Doughan - Or is there a way to convert DOM object to InputSource? – Suvasis Jan 03 '14 at 17:12
  • @Suvasis - You can evaluate an XPath against a `Document`: http://stackoverflow.com/questions/19932570/getelementsbytagname-searching-down-all-levels-of-xml-nodes/19934058#19934058 – bdoughan Jan 03 '14 at 17:24
  • 1
    Thanks Doughan & Alexandros – Suvasis Jan 03 '14 at 18:32
1

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).

Alexandros
  • 2,097
  • 20
  • 27
  • I doubt you can do what you want using JAXB though, as JAXB works using a 1-on-1 relationship with specific Class definitions. If you really MUST use JAXB, you might as well write a wrapper class that takes the JAXB object and then collects all leaf elements by using for loops and manually calling the original getters on the JAXB object. This way you will write the spagheti code once and then use this wrapper class everywhere else... – Alexandros Dec 31 '13 at 10:44
  • Thanks Alexandros for the provided solutions. Unfortunately I dont have control over XSD and client doesnt want to use DOM as we will have to hardcode all the required xml tags in an XPATH which will be tough to maintain for a big xml which is having more than 200 tags or can grow further in future. I will try out castor as you have mentioned in 3rd option to see how that works. – Suvasis Dec 31 '13 at 12:34
  • 1
    @Suvasis - You can write a generic XPath to get the leaf nodes from any XML document: http://stackoverflow.com/a/20888804/383861 – bdoughan Jan 02 '14 at 17:57
0

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

Alexandros
  • 2,097
  • 20
  • 27
  • Yes. I have the XSD but how does solve my problem. How do I use XSD to read xml elements having values? I dont want to use getters on xml converted pojo to get these values. – Suvasis Dec 31 '13 at 09:31