Cleaned up the network hint / object registration code in net_obj
[OpenSRF.git] / src / python / osrf / net_obj.py
1 from osrf.const import OSRF_JSON_PAYLOAD_KEY, OSRF_JSON_CLASS_KEY
2 from xml.sax import saxutils
3
4
5 class osrfNetworkObject(object):
6     ''' Base class for all network serializable objects '''
7     pass
8     def newFromHint(hint):
9         obj = None
10         exec('obj = osrfNetworkObject.%s()' % hint)
11         return obj
12     newFromHint = staticmethod(newFromHint)
13
14
15 ''' Global object registry '''
16 objectRegistry = {}
17
18 class osrfNetworkRegistry(object):
19     ''' Network-serializable objects must be registered.  The class
20         hint maps to a set (ordered in the case of array-base objects)
21         of field names (keys).  
22         '''
23
24     def __init__(self, hint, keys, wireProtocol):
25         global objectRegistry
26         self.hint = hint
27         self.keys = keys
28         self.wireProtocol = wireProtocol
29         objectRegistry[hint] = self
30     
31     def getRegistry(hint):
32         global objectRegistry
33         return objectRegistry.get(hint)
34     getRegistry = staticmethod(getRegistry)
35
36
37 def __makeNetworkAccessor(cls, key):
38     '''  Creates and accessor/mutator method for the given class.  
39
40         'key' is the name the method will have and represents
41         the field on the object whose data we are accessing
42         ''' 
43     def accessor(self, *args):
44         if len(args) != 0:
45             self.__data[key] = args[0]
46         return self.__data.get(key)
47     setattr(cls, key, accessor)
48
49
50
51 def __makeGetRegistry(cls, registry):
52     ''' Wraps the registry for this class inside an accessor method '''
53     def get(self):
54         return registry
55     setattr(cls, 'getRegistry', get)
56
57 def __makeGetData(cls):
58     ''' Wraps the stored data in an accessor method '''
59     def get(self):
60         return self.__data
61     setattr(cls, 'getData', get)
62         
63
64 def __osrfNetworkObjectInit(self, data={}):
65     ''' __init__ method for osrNetworkObjects.
66         If this is an array, we pull data out of the data array
67         (if there is any) and translate that into a hash internally
68         '''
69
70     self.__data = data
71     if len(data) > 0:
72         reg = self.getRegistry()
73         if reg.wireProtocol == 'array':
74             self.__data = {}
75             for i in range(len(reg.keys)):
76                 try:
77                     self.__data[reg.keys[i]] = data[i]
78                 except:
79                     self.__data[reg.keys[i]] = None
80
81
82 def osrfNetworkRegisterHint(hint, keys, type='hash'):
83     ''' Registers a new network-serializable object class.
84
85         'hint' is the class hint
86         'keys' is the list of field names on the object
87             If this is an array-based object, the field names
88             must be sorted to reflect the encoding order of the fields
89         'type' is the wire-protocol of the object.  hash or array.
90         '''
91
92     # register the class with the global registry
93     registry = osrfNetworkRegistry(hint, keys, type)
94
95     # create the new class locally with the given hint name
96     exec('class %s(osrfNetworkObject):\n\tpass' % hint)
97
98     # give the new registered class a local handle
99     cls = None
100     exec('cls = %s' % hint)
101
102     # assign an accessor/mutator for each field on the object
103     for k in keys:
104         __makeNetworkAccessor(cls, k)
105
106     # assign our custom init function
107     setattr(cls, '__init__', __osrfNetworkObjectInit)
108     __makeGetRegistry(cls, registry)
109     __makeGetData(cls)
110
111
112     # attach our new class to the osrfNetworkObject 
113     # class so others can access it
114     setattr(osrfNetworkObject, hint , cls)
115
116
117
118
119 # create a unknown object to handle unregistred types
120 osrfNetworkRegisterHint('__unknown', [], 'hash')
121
122 # -------------------------------------------------------------------
123 # Define the custom object parsing behavior 
124 # -------------------------------------------------------------------
125 def parseNetObject(obj):
126     hint = None
127     islist = False
128     try:
129         hint = obj[OSRF_JSON_CLASS_KEY]
130         obj = obj[OSRF_JSON_PAYLOAD_KEY]
131     except: pass
132     if isinstance(obj,list):
133         islist = True
134         for i in range(len(obj)):
135             obj[i] = parseNetObject(obj[i])
136     else: 
137         if isinstance(obj,dict):
138             for k,v in obj.iteritems():
139                 obj[k] = parseNetObject(v)
140
141     if hint: # Now, "bless" the object into an osrfNetworkObject
142         estr = 'obj = osrfNetworkObject.%s(obj)' % hint
143         try:
144             exec(estr)
145         except AttributeError:
146             # this object has not been registered, shove it into the default container
147             obj = osrfNetworkObject.__unknown(obj)
148
149     return obj;
150
151
152
153 def osrfObjectToXML(obj):
154     """ Returns the XML representation of an internal object."""
155     chars = []
156     __osrfObjectToXML(obj, chars)
157     return ''.join(chars)
158
159 def __osrfObjectToXML(obj, chars):
160     """ Turns an internal object into OpenSRF XML """
161
162     if obj is None:
163         chars.append('<null/>')
164         return
165
166     if isinstance(obj, unicode) or isinstance(obj, str):
167         chars.append('<string>%s</string>' % saxutils.escape(obj))
168         return
169
170     if isinstance(obj, int)  or isinstance(obj, long):
171         chars.append('<number>%d</number>' % obj)
172         return
173
174     if isinstance(obj, float):
175         chars.append('<number>%f</number>' % obj)
176         return
177
178     classHint = None
179
180     if isinstance(obj, osrfNetworkObject): 
181
182         registry = obj.getRegistry()
183         data = obj.getData()
184         hint = saxutils.escape(registry.hint)
185
186         if registry.wireProtocol == 'array':
187             chars.append("<array class_hint='%s'>" % hint)
188             for k in registry.keys:
189                 __osrfObjectToXML(data.get(k), chars)
190             chars.append('</array>')
191
192         else:
193             if registry.wireProtocol == 'hash':
194                 chars.append("<object class_hint='%s'>" % hint)
195                 for k,v in data.items():
196                     chars.append("<element key='%s'>" % saxutils.escape(k))
197                     __osrfObjectToXML(v, chars)
198                     chars.append('</element>')
199                 chars.append('</object>')
200                 
201
202     if isinstance(obj, list):
203         chars.append('<array>')
204         for i in obj:
205             __osrfObjectToXML(i, chars)
206         chars.append('</array>')
207         return
208
209     if isinstance(obj, dict):
210         chars.append('<object>')
211         for k,v in obj.items():
212             chars.append("<element key='%s'>" % saxutils.escape(k))
213             __osrfObjectToXML(v, chars)
214             chars.append('</element>')
215         chars.append('</object>')
216         return
217
218     if isinstance(obj, bool):
219         val = 'false'
220         if obj: val = 'true'
221         chars.append("<boolean value='%s'/>" % val)
222         return
223