protecting against (future) non-class root node children
[Evergreen.git] / Open-ILS / src / python / oils / utils / csedit.py
1 # -----------------------------------------------------------------------
2 # Copyright (C) 2007  Georgia Public Library Service
3 # Bill Erickson <billserickson@gmail.com>
4
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 # -----------------------------------------------------------------------
15
16 from osrf.log import *
17 from osrf.json import *
18 from oils.utils.idl import IDLParser
19 from osrf.ses import ClientSession
20 from oils.const import *
21 import re
22
23 ACTIONS = ['create', 'retrieve', 'batch_retrieve', 'update', 'delete', 'search']
24
25 class CSEditor(object):
26     """
27     Contains generated methods for accessing fieldmapper objects using the
28     following syntax:
29     
30         <ret> = <instance>.<action>_<schema>_<table>(<args>)
31
32       * <instance> = CSEditor class instance
33       * <action>   = 
34         * create 
35           <args>   = object to create
36           <ret>    = the numeric ID of the newly created object
37         * retrieve 
38           <args>   = numeric ID of the object to retrieve 
39           <ret>    = object, instance of osrf.net_obj.NetworkObject
40         * batch_retrieve
41           <args>   = list of numeric ID's
42           <ret>    = list of objects, instances of osrf.net_obj.NetworkObject
43         * update
44           <args>   = object to update
45           <ret>    = 1 on success
46         * delete
47           <args>   = object to delete
48           <ret>    = 1 on sucess
49         * search
50           <args>   = a cstore-compatible search dict.  e.g. {"id":1}.  
51             See cstore docs for the full range of search options.
52           <ret>    = a list of search results.  For standard searches, this
53                    will be a list of objects.  idlist searches will return
54                    a list of ID's.
55       * <schema>   = the name of the schema that contains the table
56       * <table>    = the name of the table
57
58     Each generated object has accessor methods corresponding to the fieldmapper
59     name attributes for a given field. The following example demonstrates how to
60     instantiate the CSEditor and a given table object, and how to invoke an
61     accessor method on that table object:
62
63     >>> import oils.utils.csedit
64     >>> import oils.utils.idl
65     >>> import osrf.system
66     >>> osrf.system.connect('/openils/conf/opensrf_core.xml', 'config.opensrf')
67     >>> oils.utils.idl.oilsParseIDL()
68     >>> oils.utils.csedit.oilsLoadCSEditor()
69     >>> editor = oils.utils.csedit.CSEditor()
70     >>> rec = editor.retrieve_biblio_record_entry(-1)
71     >>> print rec.tcn_value()
72     """
73
74     def __init__(self, **args):
75         ''' 
76             Creates a new editor object.
77
78             Support keyword arguments:
79             authtoken - Authtoken string -- used to determine 
80                 the requestor if none is provided.
81             requestor - existing user (au) object.  The requestor is 
82                 is the user performing the action.  This is important 
83                 for permission checks, logging, etc.
84             connect - boolean.  If true, a connect call is sent to the opensrf
85                 service at session create time
86             xact - boolean.  If true, a cstore transaction is created at 
87                 connect time.  xact implies connect.
88         '''
89
90         self.app = args.get('app', OILS_APP_CSTORE)
91         self.authtoken = args.get('authtoken', args.get('auth'))
92         self.requestor = args.get('requestor')
93         self.connect = args.get('connect')
94         self.xact = args.get('xact')
95         self.__session = None
96
97     def die_event(self):
98         ''' Rolls back the existing transaction, disconnects our session, 
99             and returns the last received event.
100         '''
101         pass
102
103     def checkauth(self):
104         ''' Checks the authtoken against open-ils.auth and uses the 
105             retrieved user as the requestor
106         '''
107         pass
108
109
110     # -------------------------------------------------------------------------
111     # Creates a session if one does not already exist.  If necessary, connects
112     # to the remote service and starts a transaction
113     # -------------------------------------------------------------------------
114     def session(self, ses=None):
115         ''' Creates a session if one does not already exist.  If necessary, connects
116             to the remote service and starts a transaction
117         '''
118         if not self.__session:
119             self.__session = ClientSession(self.app)
120
121         if self.connect or self.xact:
122             self.log(log_debug,'connecting to ' + self.app)
123             self.__session.connect() 
124
125         if self.xact:
126             self.log(log_info, "starting new db transaction")
127             self.request(self.app + '.transaction.begin')
128
129         return self.__session
130    
131
132     def log(self, func, string):
133         ''' Logs string with some meta info '''
134
135         s = "editor[";
136         if self.xact: s += "1|"
137         else: s += "0|"
138         if self.requestor: s += str(self.requestor.id())
139         else: s += "0"
140         s += "]"
141         func("%s %s" % (s, string))
142
143
144     def rollback(self):
145         ''' Rolls back the existing db transaction '''
146
147         if self.__session and self.xact:
148              self.log(log_info, "rolling back db transaction")
149              self.request(self.app + '.transaction.rollback')
150              self.disconnect()
151              
152     def commit(self):
153         ''' Commits the existing db transaction and disconnects '''
154
155         if self.__session and self.xact:
156             self.log(log_info, "comitting db transaction")
157             self.request(self.app + '.transaction.commit')
158             self.disconnect()
159
160
161     def disconnect(self):
162         ''' Disconnects from the remote service '''
163         if self.__session:
164             self.__session.disconnect()
165             self.__session = None
166
167
168     # -------------------------------------------------------------------------
169     # Sends a request
170     # -------------------------------------------------------------------------
171     def request(self, method, params=[]):
172
173         # XXX improve param logging here
174
175         self.log(log_info, "request %s %s" % (method, unicode(params)))
176
177         if self.xact and self.session().state != OSRF_APP_SESSION_CONNECTED:
178             self.log(log_error, "csedit lost its connection!")
179
180         val = None
181
182         try:
183             req = self.session().request2(method, params)
184             resp = req.recv()
185             val = resp.content()
186
187         except Exception, e:
188             self.log(log_error, "request error: %s" % unicode(e))
189             raise e
190
191         return val
192
193
194     # -------------------------------------------------------------------------
195     # Returns true if our requestor is allowed to perform the request action
196     # 'org' defaults to the requestors ws_ou
197     # -------------------------------------------------------------------------
198     def allowed(self, perm, org=None):
199         pass # XXX
200
201
202     def runMethod(self, action, type, arg, options={}):
203
204         method = "%s.direct.%s.%s" % (self.app, type, action)
205
206         if options.get('idlist'):
207             method = method.replace('search', 'id_list')
208             del options['idlist']
209
210         if action == 'search':
211             method += '.atomic'
212
213         if action == 'batch_retrieve':
214             method = method.replace('batch_retrieve', 'search')
215             method += '.atomic'
216             arg = {'id' : arg}
217
218         params = [arg];
219         if len(options.keys()):
220             params.append(options)
221
222         val = self.request( method, params )
223
224         return val
225
226     def rawSearch(self, args):
227         method = "%s.json_query.atomic" % self.app
228         self.log(log_debug, "rawSearch args: %s" % unicode(args))
229         return self.request(method, [args])
230
231     def rawSearch2(self, hint, fields, where, from_=None):
232         if not from_:   
233             from_ = {'%s' % hint : {}}
234
235         args = {
236             'select' : { '%s' % hint : fields },
237             'from' : from_,
238             'where' : { "+%s" % hint : where }
239         }
240         return self.rawSearch(args)
241
242
243     def fieldSearch(self, hint, fields, where):
244         return self.rawSearch2(hint, fields, where)
245
246
247
248 # -------------------------------------------------------------------------
249 # Creates a class method for each action on each type of fieldmapper object
250 # -------------------------------------------------------------------------
251 def oilsLoadCSEditor():
252     obj = IDLParser.get_parser().IDLObject
253
254     for k, fm in obj.iteritems():
255         for action in ACTIONS:
256
257             fmname = fm.fieldmapper.replace('::', '_')
258             type = fm.fieldmapper.replace('::', '.')
259             name = "%s_%s" % (action, fmname)
260
261             s = 'def %s(self, arg, **options):\n' % name
262             s += '\treturn self.runMethod("%s", "%s", arg, dict(options))\n' % (action, type)
263             s += 'setattr(CSEditor, "%s", %s)' % (name, name)
264
265             exec(s)
266