]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/python/oils/utils/idl.py
Show format icons in results and record detail pages
[working/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         # in case we're calling parse_IDL directly
82         if not IDLParser._global_parser:
83             IDLParser._global_parser = self
84
85         doc = xml.dom.minidom.parse(self.idlFile)
86         root = doc.documentElement
87
88         for child in root.childNodes:
89         
90             if child.nodeType == child.ELEMENT_NODE and child.nodeName == 'class':
91         
92                 # -----------------------------------------------------------------------
93                 # 'child' is the main class node for a fieldmapper class.
94                 # It has 'fields' and 'links' nodes as children.
95                 # -----------------------------------------------------------------------
96
97                 obj = IDLClass(
98                     self._get_attr(child, 'id'),
99                     controller = self._get_attr(child, 'controller'),
100                     fieldmapper = self._get_attr(child, 'oils_obj:fieldmapper', OILS_NS_OBJ),
101                     virtual = self._get_attr(child, 'oils_persist:virtual', OILS_NS_PERSIST),
102                     label = self._get_attr(child, 'reporter:label', OILS_NS_REPORTER),
103                     tablename = self._get_attr(child, 'oils_persist:tablename', OILS_NS_REPORTER),
104                 )
105
106
107                 self.IDLObject[obj.name] = obj
108
109                 fields = [f for f in child.childNodes if f.nodeName == 'fields']
110                 links = [f for f in child.childNodes if f.nodeName == 'links']
111
112                 fields = self.parse_fields(obj, fields[0])
113                 if len(links) > 0:
114                     self.parse_links(obj, links[0])
115
116                 osrf.net_obj.register_hint(obj.name, [f.name for f in fields], 'array')
117
118         doc.unlink()
119
120
121     def parse_links(self, idlobj, links):
122
123         for link in [l for l in links.childNodes if l.nodeName == 'link']:
124             obj = IDLLink(
125                 field = idlobj.get_field(self._get_attr(link, 'field')),
126                 reltype = self._get_attr(link, 'reltype'),
127                 key = self._get_attr(link, 'key'),
128                 map = self._get_attr(link, 'map'),
129                 class_ = self._get_attr(link, 'class')
130             )
131             idlobj.links.append(obj)
132
133
134     def parse_fields(self, idlobj, fields):
135         """Takes the fields node and parses the included field elements"""
136
137         idlobj.primary = self._get_attr(fields, 'oils_persist:primary', OILS_NS_PERSIST)
138         idlobj.sequence =  self._get_attr(fields, 'oils_persist:sequence', OILS_NS_PERSIST)
139
140         position = 0
141         for field in [l for l in fields.childNodes if l.nodeName == 'field']:
142
143             name = self._get_attr(field, 'name')
144
145             if name in ['isnew', 'ischanged', 'isdeleted']: 
146                 continue
147
148             obj = IDLField(
149                 idlobj,
150                 name = name,
151                 position = position,
152                 virtual = self._get_attr(field, 'oils_persist:virtual', OILS_NS_PERSIST),
153                 label = self._get_attr(field, 'reporter:label', OILS_NS_REPORTER),
154                 rpt_datatype = self._get_attr(field, 'reporter:datatype', OILS_NS_REPORTER),
155                 rpt_select = self._get_attr(field, 'reporter:selector', OILS_NS_REPORTER),
156                 primitive = self._get_attr(field, 'oils_persist:primitive', OILS_NS_PERSIST)
157             )
158
159             idlobj.fields.append(obj)
160             idlobj.field_map[obj.name] = obj
161             position += 1
162
163         for name in ['isnew', 'ischanged', 'isdeleted']: 
164             obj = IDLField(idlobj, 
165                 name = name, 
166                 position = position, 
167                 virtual = 'true'
168             )
169             idlobj.fields.append(obj)
170             position += 1
171
172         return idlobj.fields
173
174
175
176 class IDLClass(object):
177     def __init__(self, name, **kwargs):
178         self.name = name
179         self.controller = kwargs.get('controller')
180         self.fieldmapper = kwargs.get('fieldmapper')
181         self.virtual = kwargs.get('virtual')
182         self.label = kwargs.get('label')
183         self.tablename = kwargs.get('tablename')
184         self.primary = kwargs.get('primary')
185         self.sequence = kwargs.get('sequence')
186         self.fields = []
187         self.links = []
188         self.field_map = {}
189
190         if self.virtual and self.virtual.lower() == 'true':
191             self.virtual = True
192         else:
193             self.virtual = False
194
195     def __str__(self):
196         ''' Stringify the parsed IDL ''' # TODO: improve the format/content
197
198         s = '-'*60 + '\n'
199         s += "%s [%s] %s\n" % (self.label, self.name, self.tablename)
200         s += '-'*60 + '\n'
201         idx = 0
202         for f in self.fields:
203             s += "[%d] " % idx
204             if idx < 10: s += " "
205             s += str(f) + '\n'
206             idx += 1
207
208         return s
209
210     def get_field(self, field_name):
211         try:
212             return self.field_map[field_name]
213         except:
214             msg = "No field '%s' in IDL class '%s'" % (field_name, self.name)
215             osrf.log.log_warn(msg)
216             #raise IDLException(msg)
217
218 class IDLField(object):
219     def __init__(self, idl_class, **kwargs):
220         '''
221             @param idl_class The IDLClass object which owns this field
222         '''
223         self.idl_class = idl_class
224         self.name = kwargs.get('name')
225         self.label = kwargs.get('label')
226         self.rpt_datatype = kwargs.get('rpt_datatype')
227         self.rpt_select = kwargs.get('rpt_select')
228         self.primitive = kwargs.get('primitive')
229         self.virtual = kwargs.get('virtual')
230         self.position = kwargs.get('position')
231
232         if self.virtual and self.virtual.lower() == 'true':
233             self.virtual = True
234         else:
235             self.virtual = False
236
237     def __str__(self):
238         ''' Format as field name and data type, plus linked class for links. '''
239         s = self.name
240         if self.rpt_datatype:
241             s += " [" + self.rpt_datatype
242             if self.rpt_datatype == 'link':
243                 link = [ l for l in self.idl_class.links if l.field.name == self.name ]
244                 if len(link) > 0 and link[0].class_:
245                     s += " @%s" % link[0].class_
246             s += ']'
247         return s
248
249
250 class IDLLink(object):
251     def __init__(self, field, **kwargs):
252         '''
253             @param field The IDLField object this link references
254         '''
255         self.field = field
256         self.reltype = kwargs.get('reltype')
257         self.key = kwargs.get('key')
258         self.map = kwargs.get('map')
259         self.class_ = kwargs.get('class_')