Friday, October 29, 2010

XML Parsing with AXIOM

Recently a friend of mine inquired about what the best way is to parse XML. I have to say im not an expert in this subject but from what i have read i instantly remembered that AXIOM is a pull parser which means that when you request for a particular element within your XML document push parsers will give you that exact element where as other pull parsers will build the whole XML document before handing over the relevant document element to you. Hence AXIOM will leave the least memory foot print.

The problem was i could not find a clear and concise tutorial explaining how to deal with XML using AXIOM. After playing around with it i figured out how to manipulate XML with AXIOM which i should say is so much better that the cumbersome code you have to deal with when manipulating with DOM or JDOM.

So following i show a simple example of how to manipulate XML with AXIOM;

First off i present the XML i will be parsing

<?xml version="1.0" encoding="utf-8" ?>
<my_servers>
 <server>
  <server-name>PROD</server-name>
  <server-ip>xx.xx.xx.xx</server-ip>
  <server-port>80</server-port>
  <server-desc>Running A/S</server-desc>
 </server>

 <server>
  <server-name>PROD2</server-name>
  <server-ip>xx1.xx1.xx1.xx1</server-ip>
  <server-port>80</server-port>
  <server-desc>Running A/S</server-desc>
 </server>
</my_servers>

Next i wrote a factory method to handout StaxBuilder instances depending on the XML file you pass. I have done as such so as to minimize the task of creating new StaxBuilder instances everytime.


import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.stream.XMLStreamException;

import org.apache.axiom.om.impl.builder.StAXOMBuilder;

public class AxiomStaxBuilderFactory {

    private static Map<String, StAXOMBuilder> staxBuilderMap = new ConcurrentHashMap<String, StAXOMBuilder>();

    /**
     * The factory method stores the {@link StAXOMBuilder} instance created for each XML file<br>
     * passed in so that we do not need to create unnecessary objects every time.<br>
     * An instance of {@linkplain ConcurrentHashMap} is used so as to make the<br>
     * instances thread safe.
     * 
     * @param xmlFilePath the path of the XML file
     * @return an instance of the {@link StAXOMBuilder} from the cache or newly created
     */
    public static StAXOMBuilder getAxiomBuilderForFile(String xmlFilePath) {
        StAXOMBuilder staxBuilder = null;
        if (staxBuilderMap.containsKey(xmlFilePath)) {
            staxBuilder = staxBuilderMap.get(xmlFilePath);
        } else {
            try {
                staxBuilder = new StAXOMBuilder(new FileInputStream(xmlFilePath));
                staxBuilderMap.put(xmlFilePath, staxBuilder);
            } catch (FileNotFoundException e) {
                throw new AxiomBuilderException(e);
            } catch (XMLStreamException e) {
                throw new AxiomBuilderException(e);
            }
        }

        return staxBuilder;

    }
}



I have used a Concurrent Hash map so that this wil work well in a multi threaded application. If your not bothered with that you might as well use a normal HashMap for better performance which in this case would be negligible. I have also used a custom exception as i did not want the user to have to handle exceptions so i wrapped the exceptions thrown to my custom run time exception. Following is that code. Nothing major just a normal extension of the RuntimeException class;

/**
 * This exception class wraps all exceptions thrown from the Axiom API
 * as the user does not need to be bound by such checked exceptions.
 * @author dinuka
 *
 */
public class AxiomBuilderException extends RuntimeException {

    /**
     * 
     */
    private static final long serialVersionUID = -7853903625725204661L;

    public AxiomBuilderException(Throwable ex) {
        super(ex);
    }

    public AxiomBuilderException(String msg) {
        super(msg);
    }
}



Next off i have written a utility class to deal with the XML parsig. Ofcourse this is not needed but i just had it so that client calls will be much cleaner without having to deal with XML releated coding which would be abstracted by the utility class. Note -The current method only reads the root level elements passed in.



import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;

import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;

/**
 * The utility class provides abstraction to users so that <br>
 * the user can just pass in the xml file name and the node he/she<br>
 * wants to access and get the values without having to bother with<br>
 * boilerplate xml handling info.
 * 
 * @author dinuka
 */
public class AxiomUtil {

    /**
     * This method is used if you have for example a node with multiple children<br>
     * Note that this method assumes the node in query is within the root element
     * 
     * @param xmlFilePath the path of the xml file
     * @param nodeName the node name from which you want to retrieve values
     * @return the list containing key value pairs containing the values of the sub elements within<br>
     *         the nodeName passed in.
     */
    public static List<Map<String, String>> getNodeWithChildrenValues(String xmlFilePath, String nodeName) {
        List<Map<String, String>> valueList = new ArrayList<Map<String, String>>();

        StAXOMBuilder staxBuilder = AxiomStaxBuilderFactory.getAxiomBuilderForFile(xmlFilePath);

        OMElement documentElement = staxBuilder.getDocumentElement();
        Iterator nodeElement = documentElement.getChildrenWithName(new QName(nodeName));

        while (nodeElement.hasNext()) {
            OMElement om = (OMElement) nodeElement.next();

            Iterator it = om.getChildElements();
            Map<String, String> valueMap = new HashMap<String, String>();
            while (it.hasNext()) {
                OMElement el = (OMElement) it.next();

                valueMap.put(el.getLocalName(), el.getText());

            }

            valueList.add(valueMap);
        }
        return valueList;
    }

}



And finally i give to you a sample class to test out the XML parsing example i have presented to you here.



import java.util.List;
import java.util.Map;

/**
 * Test class depicting the use of Axiom parsing XML
 * 
 * @author dinuka
 */
public class testServerConfigXML {

    public static void main(String argv[]) {

        List<Map<String, String>> values = AxiomUtil.getNodeWithChildrenValues("/home/dinuka/serverInfo.xml", "server");

        for (Map<String, String> mapVals : values) {
            for (String keys : mapVals.keySet()) {
                System.out.println(keys + "=" + mapVals.get(keys));
            }
        }

    }

}


Thats it. If you have any queries or any improvement points you see pls do leave a comment which would be highly appreciated. Hope this helps anyone out there looking for a similar basic tutorial on AXIOM XML parsing.


Cheers