/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, availible at the root * application directory. */ package org.geoserver.rest.format; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import org.jdom.output.Format; import org.jdom.output.XMLOutputter; import org.restlet.data.MediaType; /** * A format that automatically converts a map into an XML document and vice versa. * <p> * The resulting XML document contains elements whose names match the keys of the map. * The contents of each element are mapped from the map values as follows: * <ul> * <li> A text value for regular "primitive" objects</li> * <li> A sub document structure for maps </li> * <li> A collection of <entry> elements for a collection </li> * <ul> * </p> * <p> * The original map can be reconstructed from the XML document. However due to the use * of the element name "entry" to indicate a collection, it is not recommended that any * keys of the map use this string. * </p> * * @author David Winslow <dwinslow@openplans.org> */ public class MapXMLFormat extends StreamDataFormat { /** the name of the root element of the XML document */ String myRootName; /** * Creates a new format that names the root element of the resulting document "root". */ public MapXMLFormat(){ this("root"); } /** * Creates a new format specifying the name of the root element of the resulting XML document. */ public MapXMLFormat(String s){ super( MediaType.APPLICATION_XML ); myRootName = s; } @Override protected void write(Object object, OutputStream out) throws IOException { Element root = new Element(myRootName); final Document doc = new Document(root); insert(root, object); XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat()); outputter.output(doc, out); } /** * Generate the JDOM element needed to represent an object and insert it into the parent element given. * @todo This method is recursive and could cause stack overflow errors for large input maps. * * @param elem the parent Element into which to insert the created JDOM element * @param o the Object to be converted */ private final void insert(Element elem, Object o){ if (o instanceof Map){ Iterator it = ((Map)o).entrySet().iterator(); while (it.hasNext()){ Map.Entry entry = (Map.Entry)it.next(); Element newElem = new Element(entry.getKey().toString()); insert(newElem, entry.getValue()); elem.addContent(newElem); } } else if (o instanceof Collection) { Iterator it = ((Collection)o).iterator(); while (it.hasNext()){ Element newElem = new Element("entry"); Object entry = it.next(); insert(newElem, entry); elem.addContent(newElem); } } else { elem.addContent(o == null ? "" : o.toString()); } } @Override protected Object read(InputStream in) throws IOException { Object result = null; SAXBuilder builder = new SAXBuilder(); Document doc; try { doc = builder.build(in); } catch (JDOMException e) { throw (IOException) new IOException("Error building document").initCause( e ); } Element elem = doc.getRootElement(); result = convert(elem); return result; } /** * Interpret XML and convert it back to a Java collection. * * @param elem a JDOM element * @return the Object produced by interpreting the XML */ private Object convert(Element elem){ List children = elem.getChildren(); if (children.size() == 0){ if (elem.getContent().size() == 0){ return null; } else { return elem.getText(); } } else if (children.get(0) instanceof Element){ Element child = (Element)children.get(0); if (child.getName().equals("entry")){ List l = new ArrayList(); Iterator it = elem.getChildren("entry").iterator(); while(it.hasNext()){ Element curr = (Element)it.next(); l.add(convert(curr)); } return l; } else { Map m = new HashMap(); Iterator it = children.iterator(); while (it.hasNext()){ Element curr = (Element)it.next(); m.put(curr.getName(), convert(curr)); } return m; } } throw new RuntimeException("Unable to parse XML"); } }