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