commit b9809f7c6f20662294ec5b91b2cbdb8309a9aeec
Author: Faidon Liambotis <faidon@noc.grnet.gr>
Date:   Fri Nov 26 23:33:15 2010 +0200

    Add our own XML emitter
    
    OpenStack's API XML isn't merely an XML representation of the data
    structures and therefore Piston's XML Emitter can't be used as-is.
    
    The XML generation code from OpenStack's Nova is guranteed(?) to be
    compatible, so re-use their code and adapt it into an Emitter subclass.
    
    Additionally, enable our own Resource implementation that also checks
    the Accept header; it was there before but was dead code due to the
    missing Emitter.
    
    Finally, make sure that we use "application/xml" instead of "text/xml",
    as per the specification

diff --git a/api/emitter.py b/api/emitter.py
index be74858..504bc1f 100644
--- a/api/emitter.py
+++ b/api/emitter.py
@@ -3,7 +3,9 @@
 # Copyright © 2010 Greek Research and Technology Network
 #
 
+from piston.emitters import Emitter, Mimer
 from piston.resource import Resource as BaseResource
+from xml.dom import minidom
 import re
 
 _accept_re = re.compile(r'([^\s;,]+)(?:[^,]*?;\s*q=(\d*(?:\.\d+)?))?')
@@ -30,8 +32,6 @@ def parse_accept_header(value):
 
     return result
 
-# XXX: works as intended but not used since piston's XMLEmitter doesn't output
-# XML according to our spec :(
 class Resource(BaseResource):
     def determine_emitter(self, request, *args, **kwargs):
         """
@@ -46,7 +46,7 @@ class Resource(BaseResource):
         specification.
         """
 
-        em = request.GET.get('format', 'xml')
+        em = request.GET.get('format', 'json')
         if 'emitter_format' in kwargs:
             em = kwargs.pop('emitter_format')
         elif 'HTTP_ACCEPT' in request.META:
@@ -59,3 +59,60 @@ class Resource(BaseResource):
                     break
 
         return em
+
+class OSXMLEmitter(Emitter):
+    """
+    Custom XML Emitter that handles some special stuff needed by the API.
+
+    Shamelessly stolen^Wborrowed code (sans Piston integration) by OpenStack's
+    Nova project and hence:
+
+    Copyright 2010 United States Government as represented by the
+    Administrator of the National Aeronautics and Space Administration.
+    Copyright 2010 OpenStack LLC.
+
+    and licensed under the Apache License, Version 2.0
+    """
+
+    _metadata = {
+            "server": [ "id", "imageId", "name", "flavorId", "hostId",
+                        "status", "progress", "progress" ],
+            "flavor": [ "id", "name", "ram", "disk" ],
+            "image": [ "id", "name", "updated", "created", "status",
+                       "serverId", "progress" ],
+        }
+
+    def _to_xml_node(self, doc, nodename, data):
+        """Recursive method to convert data members to XML nodes."""
+        result = doc.createElement(nodename)
+        if type(data) is list:
+            if nodename.endswith('s'):
+                singular = nodename[:-1]
+            else:
+                singular = 'item'
+            for item in data:
+                node = self._to_xml_node(doc, singular, item)
+                result.appendChild(node)
+        elif type(data) is dict:
+            attrs = self._metadata.get(nodename, {})
+            for k, v in data.items():
+                if k in attrs:
+                    result.setAttribute(k, str(v))
+                else:
+                    node = self._to_xml_node(doc, k, v)
+                    result.appendChild(node)
+        else: # atom
+            node = doc.createTextNode(str(data))
+            result.appendChild(node)
+        return result
+
+    def render(self, request):
+        data = self.construct()
+        # We expect data to contain a single key which is the XML root.
+        root_key = data.keys()[0]
+        doc = minidom.Document()
+        node = self._to_xml_node(doc, root_key, data[root_key])
+        return node.toprettyxml(indent='    ')
+
+Emitter.register('xml', OSXMLEmitter, 'application/xml')
+Mimer.register(lambda *a: None, ('application/xml',))
diff --git a/api/faults.py b/api/faults.py
index 637dba2..9394063 100644
--- a/api/faults.py
+++ b/api/faults.py
@@ -77,7 +77,7 @@ class _fault_factory(object):
         m['details'] = ''
 
         # piston > 0.2.2 does the serialization for us, but be compatible
-        # XXX: this won't work when we start supporting XML
+        # 'till the next version gets released. XXX: this doesn't do XML!
         message = simplejson.dumps({ attr: m }, ensure_ascii=False, indent=4)
         code = m['code']
         response = HttpResponse(message, status=code)
diff --git a/api/urls.py b/api/urls.py
index 8b3c847..a0a7fb1 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -4,7 +4,7 @@
 #
 
 from django.conf.urls.defaults import *
-from piston.resource import Resource
+from synnefo.api.emitter import Resource
 from synnefo.api.handlers import *
 from synnefo.api.authentication import TokenAuthentication
 from synnefo.api.faults import fault
