b71ee690980ab2b57c7ff1014d5a9004ed5a8f0c
[OpenSRF.git] / src / python / osrf / net_obj.py
1 from osrf.const import OSRF_JSON_PAYLOAD_KEY, OSRF_JSON_CLASS_KEY
2 import re
3 from xml.sax import saxutils
4
5
6 # -----------------------------------------------------------
7 # Define the global network-class registry
8 # -----------------------------------------------------------
9
10
11 class NetworkRegistry(object):
12     ''' Network-serializable objects must be registered.  The class
13         hint maps to a set (ordered in the case of array-base objects)
14         of field names (keys).  
15         '''
16
17     # Global object registry 
18     registry = {}
19
20     def __init__(self, hint, keys, protocol):
21         self.hint = hint
22         self.keys = keys
23         self.protocol = protocol
24         NetworkRegistry.registry[hint] = self
25     
26     @staticmethod
27     def get_registry(hint):
28         return NetworkRegistry.registry.get(hint)
29
30 # -----------------------------------------------------------
31 # Define the base class for all network-serializable objects
32 # -----------------------------------------------------------
33
34 class NetworkObject(object):
35     ''' Base class for all network serializable objects '''
36
37     # link to our registry object for this registered class
38     registry = None
39
40     def __init__(self, data=None):
41         ''' If this is an array, we pull data out of the data array
42             (if there is any) and translate that into a hash internally '''
43
44         self._data = data
45         if not data: self._data = {}
46         if isinstance(data, list):
47             self.import_array_data(list)
48
49     def import_array_data(self, data):
50         ''' If an array-based object is created with an array
51             of data, cycle through and load the data '''
52
53         self._data = {}
54         if len(data) == 0:
55             return
56
57         reg = self.get_registry()
58         if reg.protocol == 'array':
59             for entry in range(len(reg.keys)):
60                 if len(data) > entry:
61                     break
62                 self.set_field(reg.keys[entry], data[entry])
63
64     def get_data(self):
65         ''' Returns the full dataset for this object as a dict '''
66         return self._data
67
68     def set_field(self, field, value):
69         self._data[field] = value
70
71     def get_field(self, field):
72         return self._data.get(field)
73
74     def get_registry(self):
75         ''' Returns the registry object for this registered class '''
76         return self.__class__.registry
77
78 def new_object_from_hint(hint):
79     ''' Given a hint, this will create a new object of that 
80         type and return it.  If this hint is not registered,
81         an object of type NetworkObject.__unknown is returned'''
82     try:
83         obj = None
84         exec('obj = NetworkObject.%s()' % hint)
85         return obj
86     except AttributeError:
87         return NetworkObject.__unknown()
88
89 def __make_network_accessor(cls, key):
90     ''' Creates and accessor/mutator method for the given class.  
91         'key' is the name the method will have and represents
92         the field on the object whose data we are accessing ''' 
93     def accessor(self, *args):
94         if len(args) != 0:
95             self.set_field(key, args[0])
96         return self.get_field(key)
97     setattr(cls, key, accessor)
98
99
100 def register_hint(hint, keys, type='hash'):
101     ''' Registers a new network-serializable object class.
102
103         'hint' is the class hint
104         'keys' is the list of field names on the object
105             If this is an array-based object, the field names
106             must be sorted to reflect the encoding order of the fields
107         'type' is the wire-protocol of the object.  hash or array.
108         '''
109
110     # register the class with the global registry
111     registry = NetworkRegistry(hint, keys, type)
112
113     # create the new class locally with the given hint name
114     exec('class %s(NetworkObject):\n\tpass' % hint)
115
116     # give the new registered class a local handle
117     cls = None
118     exec('cls = %s' % hint)
119
120     # assign an accessor/mutator for each field on the object
121     for k in keys:
122         __make_network_accessor(cls, k)
123
124     # attach our new class to the NetworkObject 
125     # class so others can access it
126     setattr(NetworkObject, hint , cls)
127     cls.registry = registry
128
129
130
131
132 # create a unknown object to handle unregistred types
133 register_hint('__unknown', [], 'hash')
134
135 # -------------------------------------------------------------------
136 # Define the custom object parsing behavior 
137 # -------------------------------------------------------------------
138 def parse_net_object(obj):
139     
140     try:
141         hint = obj[OSRF_JSON_CLASS_KEY]
142         sub_object = obj[OSRF_JSON_PAYLOAD_KEY]
143         reg = NetworkRegistry.get_registry(hint)
144
145         obj = {}
146
147         if reg.protocol == 'array':
148             for entry in range(len(reg.keys)):
149                 if len(sub_object) > entry:
150                     obj[reg.keys[entry]] = parse_net_object(sub_object[entry])
151                 else:
152                     obj[reg.keys[entry]] = None
153         else:
154             for key in reg.keys:
155                 obj[key] = parse_net_object(sub_object.get(key))
156
157         estr = 'obj = NetworkObject.%s(obj)' % hint
158         try:
159             exec(estr)
160         except:
161             # this object has not been registered, shove it into the default container
162             obj = NetworkObject.__unknown(obj)
163
164         return obj
165
166     except:
167         pass
168
169     # the current object does not have a class hint
170     if isinstance(obj, list):
171         for entry in range(len(obj)):
172             obj[entry] = parse_net_object(obj[entry])
173
174     else:
175         if isinstance(obj, dict):
176             for key, value in obj.iteritems():
177                 obj[key] = parse_net_object(value)
178
179     return obj
180
181
182 def to_xml(obj):
183     """ Returns the XML representation of an internal object."""
184     chars = []
185     __to_xml(obj, chars)
186     return ''.join(chars)
187
188 def __to_xml(obj, chars):
189     """ Turns an internal object into OpenSRF XML """
190
191     if obj is None:
192         chars.append('<null/>')
193         return
194
195     if isinstance(obj, unicode) or isinstance(obj, str):
196         chars.append('<string>%s</string>' % saxutils.escape(obj))
197         return
198
199     if isinstance(obj, int)  or isinstance(obj, long):
200         chars.append('<number>%d</number>' % obj)
201         return
202
203     if isinstance(obj, float):
204         chars.append('<number>%f</number>' % obj)
205         return
206
207     if isinstance(obj, NetworkObject): 
208
209         registry = obj.get_registry()
210         data = obj.get_data()
211         hint = saxutils.escape(registry.hint)
212
213         if registry.protocol == 'array':
214             chars.append("<array class_hint='%s'>" % hint)
215             for key in registry.keys:
216                 __to_xml(data.get(key), chars)
217             chars.append('</array>')
218
219         else:
220             if registry.protocol == 'hash':
221                 chars.append("<object class_hint='%s'>" % hint)
222                 for key, value in data.items():
223                     chars.append("<element key='%s'>" % saxutils.escape(key))
224                     __to_xml(value, chars)
225                     chars.append('</element>')
226                 chars.append('</object>')
227                 
228
229     if isinstance(obj, list):
230         chars.append('<array>')
231         for entry in obj:
232             __to_xml(entry, chars)
233         chars.append('</array>')
234         return
235
236     if isinstance(obj, dict):
237         chars.append('<object>')
238         for key, value in obj.items():
239             chars.append("<element key='%s'>" % saxutils.escape(key))
240             __to_xml(value, chars)
241             chars.append('</element>')
242         chars.append('</object>')
243         return
244
245     if isinstance(obj, bool):
246         val = 'false'
247         if obj:
248             val = 'true'
249         chars.append("<boolean value='%s'/>" % val)
250         return
251
252 def find_object_path(obj, path, idx=None):
253     """Searches an object along the given path for a value to return.
254
255     Path separators can be '/' or '.', '/' is tried first."""
256
257     parts = []
258
259     if re.search('/', path):
260         parts = path.split('/')
261     else:
262         parts = path.split('.')
263
264     for part in parts:
265         try:
266             val = obj[part]
267         except:
268             return None
269         if isinstance(val, str): 
270             return val
271         if isinstance(val, list):
272             if idx != None:
273                 return val[idx]
274             return val
275         if isinstance(val, dict):
276             obj = val
277         else:
278             return val
279
280     return obj