]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/python/oils/utils/idl.py
LP1908763 Survey column sorting broken
[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 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     """Exception thrown when parsing the IDL file"""
22     pass
23
24 class IDLParser(object):
25     """Evergreen fieldmapper IDL file parser"""
26
27     # ------------------------------------------------------------
28     # static methods and variables for managing a global parser
29     # ------------------------------------------------------------
30     _global_parser = None
31
32     @staticmethod
33     def get_parser():
34         ''' Returns the global IDL parser object '''
35         if IDLParser._global_parser is None:
36             raise IDLException("IDL has not been parsed")
37         return IDLParser._global_parser
38
39     @staticmethod
40     def parse():
41         ''' Finds the path to the IDL file from the OpenSRF settings 
42             server, parses the IDL file, and uses the parsed data as
43             the global IDL repository '''
44         if IDLParser._global_parser is None:
45             parser = IDLParser()
46             idl_path = osrf.ses.ClientSession.atomic_request(
47                 OILS_APP_ACTOR, 'opensrf.open-ils.fetch_idl.file')
48             parser.set_idl(idl_path)
49             parser.parse_idl()
50             IDLParser._global_parser = parser
51
52     @staticmethod
53     def get_class(class_name):
54         ''' Returns the IDLClass object with the given 
55             network hint / IDL class name.
56             @param The class ID from the IDL
57             '''
58         return IDLParser.get_parser().idl_object[class_name]
59
60     # ------------------------------------------------------------
61     # instance methods
62     # ------------------------------------------------------------
63
64     def __init__(self):
65         """Initializes the IDL object"""
66         self.idl_object = {}
67         self.idl_file = None
68
69     def set_IDL(self, idlfile):
70         """Deprecated non-PEP8 version of set_idl()"""
71         self.set_idl(idlfile)
72
73     def set_idl(self, idlfile):
74         """Specifies the filename or file that contains the IDL"""
75         self.idl_file = idlfile
76
77     def parse_IDL(self):
78         """Deprecated non-PEP8 version of parse_idl()"""
79         self.parse_idl()
80
81     def parse_idl(self):
82         """Parses the IDL file and builds class, field, and link objects"""
83
84         # in case we're calling parse_idl directly
85         if not IDLParser._global_parser:
86             IDLParser._global_parser = self
87
88         doc = xml.dom.minidom.parse(self.idl_file)
89         root = doc.documentElement
90
91         for child in root.childNodes:
92         
93             if child.nodeType == child.ELEMENT_NODE and child.nodeName == 'class':
94         
95                 # -----------------------------------------------------------------------
96                 # 'child' is the main class node for a fieldmapper class.
97                 # It has 'fields' and 'links' nodes as children.
98                 # -----------------------------------------------------------------------
99
100                 obj = IDLClass(
101                     _attr(child, 'id'),
102                     controller = _attr(child, 'controller'),
103                     fieldmapper = _attr(child, 'oils_obj:fieldmapper', OILS_NS_OBJ),
104                     virtual = _attr(child, 'oils_persist:virtual', OILS_NS_PERSIST),
105                     label = _attr(child, 'reporter:label', OILS_NS_REPORTER),
106                     tablename = _attr(child, 'oils_persist:tablename', OILS_NS_PERSIST),
107                     field_safe = _attr(child, 'oils_persist:field_safe', OILS_NS_PERSIST),
108                 )
109
110                 self.idl_object[obj.name] = obj
111
112                 fields = [f for f in child.childNodes if f.nodeName == 'fields']
113                 links = [f for f in child.childNodes if f.nodeName == 'links']
114
115                 fields = _parse_fields(obj, fields[0])
116                 if len(links) > 0:
117                     _parse_links(obj, links[0])
118
119                 osrf.net_obj.register_hint(
120                     obj.name, [f.name for f in fields], 'array'
121                 )
122
123         doc.unlink()
124
125
126 class IDLClass(object):
127     """Represents a class in the fieldmapper IDL"""
128
129     def __init__(self, name, **kwargs):
130         self.name = name
131         self.controller = kwargs.get('controller')
132         self.fieldmapper = kwargs.get('fieldmapper')
133         self.virtual = _to_bool(kwargs.get('virtual'))
134         self.label = kwargs.get('label')
135         self.tablename = kwargs.get('tablename')
136         self.primary = kwargs.get('primary')
137         self.sequence = kwargs.get('sequence')
138         self.field_safe = _to_bool(kwargs.get('field_safe'))
139         self.fields = []
140         self.links = []
141         self.field_map = {}
142
143     def __str__(self):
144         ''' Stringify the parsed IDL ''' # TODO: improve the format/content
145
146         idl = '-'*60 + '\n'
147         idl += "%s [%s] %s\n" % (self.label, self.name, self.tablename)
148         idl += '-'*60 + '\n'
149         idx = 0
150         for field in self.fields:
151             idl += "[%d] " % idx
152             if idx < 10:
153                 idl += " "
154             idl += str(field) + '\n'
155             idx += 1
156
157         return idl
158
159     def get_field(self, field_name):
160         """Return the specified field from the class"""
161
162         try:
163             return self.field_map[field_name]
164         except:
165             msg = "No field '%s' in IDL class '%s'" % (field_name, self.name)
166             osrf.log.log_warn(msg)
167             #raise IDLException(msg)
168
169 class IDLField(object):
170     """Represents a field in a class in the fieldmapper IDL"""
171
172     def __init__(self, idl_class, **kwargs):
173         '''
174             @param idl_class The IDLClass object which owns this field
175         '''
176         self.idl_class = idl_class
177         self.name = kwargs.get('name')
178         self.label = kwargs.get('label')
179         self.rpt_datatype = kwargs.get('rpt_datatype')
180         self.rpt_select = kwargs.get('rpt_select')
181         self.primitive = kwargs.get('primitive')
182         self.virtual = kwargs.get('virtual')
183         self.position = kwargs.get('position')
184
185         if self.virtual and str(self.virtual).lower() == 'true':
186             self.virtual = True
187         else:
188             self.virtual = False
189
190     def __str__(self):
191         ''' Format as field name and data type, plus linked class for links. '''
192         field = self.name
193         if self.rpt_datatype:
194             field += " [" + self.rpt_datatype
195             if self.rpt_datatype == 'link':
196                 link = [ 
197                     l for l in self.idl_class.links
198                         if l.field.name == self.name 
199                 ]
200                 if len(link) > 0 and link[0].class_:
201                     field += " @%s" % link[0].class_
202             field += ']'
203         return field
204
205
206 class IDLLink(object):
207     """Represents a link between objects defined in the IDL"""
208
209     def __init__(self, field, **kwargs):
210         '''
211             @param field The IDLField object this link references
212         '''
213         self.field = field
214         self.reltype = kwargs.get('reltype')
215         self.key = kwargs.get('key')
216         self.map = kwargs.get('map')
217         self.class_ = kwargs.get('class_')
218
219 def _attr(node, name, namespace=None):
220     """ Find the attribute value on a given node 
221         Namespace is ignored for now;
222         not sure if minidom has namespace support.
223         """
224     attr = node.attributes.get(name)
225     if attr:
226         return attr.nodeValue
227     return None
228
229 def _parse_links(idlobj, links):
230     """Parses the links between objects defined in the IDL"""
231
232     for link in [l for l in links.childNodes if l.nodeName == 'link']:
233         obj = IDLLink(
234             field = idlobj.get_field(_attr(link, 'field')),
235             reltype = _attr(link, 'reltype'),
236             key = _attr(link, 'key'),
237             map = _attr(link, 'map'),
238             class_ = _attr(link, 'class')
239         )
240         idlobj.links.append(obj)
241
242 def _parse_fields(idlobj, fields):
243     """Takes the fields node and parses the included field elements"""
244
245     idlobj.primary = _attr(fields, 'oils_persist:primary', OILS_NS_PERSIST)
246     idlobj.sequence =  _attr(fields, 'oils_persist:sequence', OILS_NS_PERSIST)
247
248     position = 0
249     for field in [l for l in fields.childNodes if l.nodeName == 'field']:
250
251         name = _attr(field, 'name')
252
253         if name in ['isnew', 'ischanged', 'isdeleted']: 
254             continue
255
256         obj = IDLField(
257             idlobj,
258             name = name,
259             position = position,
260             virtual = _attr(field, 'oils_persist:virtual', OILS_NS_PERSIST),
261             label = _attr(field, 'reporter:label', OILS_NS_REPORTER),
262             rpt_datatype = _attr(field, 'reporter:datatype', OILS_NS_REPORTER),
263             rpt_select = _attr(field, 'reporter:selector', OILS_NS_REPORTER),
264             primitive = _attr(field, 'oils_persist:primitive', OILS_NS_PERSIST)
265         )
266
267         idlobj.fields.append(obj)
268         idlobj.field_map[obj.name] = obj
269         position += 1
270
271     for name in ['isnew', 'ischanged', 'isdeleted']: 
272         obj = IDLField(idlobj, 
273             name = name, 
274             position = position, 
275             virtual = 'true'
276         )
277         idlobj.fields.append(obj)
278         position += 1
279
280     return idlobj.fields
281
282 def _to_bool(field):
283     """Converts a string from the DOM into a boolean value. """
284
285     if field and str(field).lower() == 'true':
286         return True
287     return False
288