protecting against (future) non-class root node children
[Evergreen.git] / Open-ILS / src / python / oils / utils / idl.py
1 """
2 Parses an Evergreen fieldmapper IDL file and builds a global registry of
3 objects representing that IDL.
4
5 Typical usage:
6
7 >>> import osrf.system
8 >>> import oils.utils.idl
9 >>> osrf.system.connect('/openils/conf/opensrf_core.xml', 'config.opensrf')
10 >>> oils.utils.idl.IDLParser.parse()
11 >>> # 'bre' is a network registry hint, or class ID in the IDL file
12 ... print oils.utils.idl.IDLParser.get_class('bre').tablename
13 biblio.record_entry
14 """
15 import sys, string, xml.dom.minidom
16 #import osrf.net_obj, osrf.log, osrf.set, osrf.ex, osrf.ses
17 import osrf.net_obj, osrf.log, osrf.ex, osrf.ses
18 from oils.const import OILS_NS_OBJ, OILS_NS_PERSIST, OILS_NS_REPORTER, OILS_APP_ACTOR
19
20 class IDLException(osrf.ex.OSRFException):
21     pass
22
23 class IDLParser(object):
24
25     # ------------------------------------------------------------
26     # static methods and variables for managing a global parser
27     # ------------------------------------------------------------
28     _global_parser = None
29
30     @staticmethod
31     def get_parser():
32         ''' Returns the global IDL parser object '''
33         if IDLParser._global_parser is None:
34             raise IDLException("IDL has not been parsed")
35         return IDLParser._global_parser
36
37     @staticmethod
38     def parse():
39         ''' Finds the path to the IDL file from the OpenSRF settings 
40             server, parses the IDL file, and uses the parsed data as
41             the global IDL repository '''
42         if IDLParser._global_parser is None:
43             parser = IDLParser()
44             idl_path = osrf.ses.ClientSession.atomic_request(
45                 OILS_APP_ACTOR, 'opensrf.open-ils.fetch_idl.file')
46             parser.set_IDL(idl_path)
47             parser.parse_IDL()
48             IDLParser._global_parser = parser
49
50     @staticmethod
51     def get_class(class_name):
52         ''' Returns the IDLClass object with the given 
53             network hint / IDL class name.
54             @param The class ID from the IDL
55             '''
56         return IDLParser.get_parser().IDLObject[class_name]
57
58     # ------------------------------------------------------------
59     # instance methods
60     # ------------------------------------------------------------
61
62     def __init__(self):
63         self.IDLObject = {}
64
65     def set_IDL(self, file):
66         self.idlFile = file
67
68     def _get_attr(self, node, name, ns=None):
69         """ Find the attribute value on a given node 
70             Namespace is ignored for now.. 
71             not sure if minidom has namespace support.
72             """
73         attr = node.attributes.get(name)
74         if attr:
75             return attr.nodeValue
76         return None
77
78     def parse_IDL(self):
79         """Parses the IDL file and builds class, field, and link objects"""
80
81         doc = xml.dom.minidom.parse(self.idlFile)
82         root = doc.documentElement
83
84         for child in root.childNodes:
85         
86             if child.nodeType == child.ELEMENT_NODE and child.nodeName == 'class':
87         
88                 # -----------------------------------------------------------------------
89                 # 'child' is the main class node for a fieldmapper class.
90                 # It has 'fields' and 'links' nodes as children.
91                 # -----------------------------------------------------------------------
92
93                 obj = IDLClass(
94                     self._get_attr(child, 'id'),
95                     controller = self._get_attr(child, 'controller'),
96                     fieldmapper = self._get_attr(child, 'oils_obj:fieldmapper', OILS_NS_OBJ),
97                     virtual = self._get_attr(child, 'oils_persist:virtual', OILS_NS_PERSIST),
98                     label = self._get_attr(child, 'reporter:label', OILS_NS_REPORTER),
99                     tablename = self._get_attr(child, 'oils_persist:tablename', OILS_NS_REPORTER),
100                 )
101
102
103                 self.IDLObject[obj.name] = obj
104
105                 fields = [f for f in child.childNodes if f.nodeName == 'fields']
106                 links = [f for f in child.childNodes if f.nodeName == 'links']
107
108                 keys = self.parse_fields(obj, fields[0])
109                 if len(links) > 0:
110                     self.parse_links(obj, links[0])
111
112                 osrf.net_obj.register_hint(obj.name, keys, 'array')
113
114         doc.unlink()
115
116
117     def parse_links(self, idlobj, links):
118
119         for link in [l for l in links.childNodes if l.nodeName == 'link']:
120             obj = IDLLink(
121                 field = idlobj.get_field(self._get_attr(link, 'field')),
122                 rel_type = self._get_attr(link, 'rel_type'),
123                 key = self._get_attr(link, 'key'),
124                 map = self._get_attr(link, 'map')
125             )
126             idlobj.links.append(obj)
127
128
129     def parse_fields(self, idlobj, fields):
130         """Takes the fields node and parses the included field elements"""
131
132         keys = []
133
134         idlobj.primary = self._get_attr(fields, 'oils_persist:primary', OILS_NS_PERSIST)
135         idlobj.sequence =  self._get_attr(fields, 'oils_persist:sequence', OILS_NS_PERSIST)
136
137         # pre-flesh the array of keys to accomodate random index insertions
138         for field in fields.childNodes:
139             if field.nodeType == field.ELEMENT_NODE:
140                 keys.append(None)
141         
142         for field in [l for l in fields.childNodes if l.nodeName == 'field']:
143
144             obj = IDLField(
145                 idlobj,
146                 name = self._get_attr(field, 'name'),
147                 position = int(self._get_attr(field, 'oils_obj:array_position', OILS_NS_OBJ)),
148                 virtual = self._get_attr(field, 'oils_persist:virtual', OILS_NS_PERSIST),
149                 label = self._get_attr(field, 'reporter:label', OILS_NS_REPORTER),
150                 rpt_datatype = self._get_attr(field, 'reporter:datatype', OILS_NS_REPORTER),
151                 rpt_select = self._get_attr(field, 'reporter:selector', OILS_NS_REPORTER),
152                 primitive = self._get_attr(field, 'oils_persist:primitive', OILS_NS_PERSIST)
153             )
154
155             try:
156                 keys[obj.position] = obj.name
157             except Exception, e:
158                 osrf.log.log_error("parse_fields(): position out of range.  pos=%d : key-size=%d" % (obj.position, len(keys)))
159                 raise e
160
161             idlobj.fields.append(obj)
162
163         return keys
164
165
166
167 class IDLClass(object):
168     def __init__(self, name, **kwargs):
169         self.name = name
170         self.controller = kwargs.get('controller')
171         self.fieldmapper = kwargs.get('fieldmapper')
172         self.virtual = kwargs.get('virtual')
173         self.label = kwargs.get('label')
174         self.tablename = kwargs.get('tablename')
175         self.primary = kwargs.get('primary')
176         self.sequence = kwargs.get('sequence')
177         self.fields = []
178         self.links = []
179
180         if self.virtual and self.virtual.lower() == 'true':
181             self.virtual = True
182         else:
183             self.virtual = False
184
185     def get_field(self, field_name):
186         try:
187             return [f for f in self.fields if f.name == field_name][0]
188         except:
189             msg = "No field '%s' in IDL class '%s'" % (field_name, self.name)
190             osrf.log.log_warn(msg)
191             #raise IDLException(msg)
192
193 class IDLField(object):
194     def __init__(self, idl_class, **kwargs):
195         '''
196             @param idl_class The IDLClass object which owns this field
197         '''
198         self.idl_class = idl_class
199         self.name = kwargs.get('name')
200         self.label = kwargs.get('label')
201         self.rpt_datatype = kwargs.get('rpt_datatype')
202         self.rpt_select = kwargs.get('rpt_select')
203         self.primitive = kwargs.get('primitive')
204         self.virtual = kwargs.get('virtual')
205         self.position = kwargs.get('position')
206
207         if self.virtual and self.virtual.lower() == 'true':
208             self.virtual = True
209         else:
210             self.virtual = False
211
212
213 class IDLLink(object):
214     def __init__(self, field, **kwargs):
215         '''
216             @param field The IDLField object this link references
217         '''
218         self.field = field
219         self.rel_type = kwargs.get('rel_type')
220         self.key = kwargs.get('key')
221         self.map = kwargs.get('map')
222
223