implemented the majority of server-side python. still need to add settings server...
[OpenSRF.git] / src / python / osrf / app.py
1 # -----------------------------------------------------------------------
2 # Copyright (C) 2008  Equinox Software, Inc.
3 # Bill Erickson <erickson@esilibrary.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 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  
18 # 02110-1301, USA
19 # -----------------------------------------------------------------------
20
21 import time
22 import osrf.log, osrf.ses, osrf.json
23
24
25 class Method(object):
26     def __init__(self, **kwargs):
27         self.name = kwargs['api_name']
28         self.handler = kwargs['method']
29         self.stream = kwargs.get('stream', False)
30         self.argc = kwargs.get('argc', 0)
31         self.atomic = kwargs.get('atomic', False)
32
33     def get_func(self):
34         ''' Returns the function handler reference '''
35         return getattr(Application.application, self.handler)
36
37     def get_doc(self):
38         ''' Returns the function documentation '''
39         return self.get_func().func_doc
40         
41
42
43 class Application(object):
44     ''' Base class for OpenSRF applications.  Provides static methods
45         for loading and registering applications as well as common 
46         applicatoin methods. '''
47
48     # global application handle
49     application = None
50     name = None
51     methods = {}
52
53     def __init__(self):
54         ''' Sets the application name and loads the application methods '''
55         self.name = None
56
57     def global_init(self):
58         ''' Override this method to run code at application startup '''
59         pass
60
61     def child_init(self):
62         ''' Override this method to run code at child startup.
63             This is useful for initializing database connections or
64             initializing other persistent resources '''
65         pass
66
67     def child_exit(self):
68         ''' Override this method to run code at process exit time.
69             This is useful for cleaning up resources like databaes 
70             handles, etc. '''
71         pass
72
73
74     @staticmethod
75     def load(name, module_name):
76         ''' Loads the provided application module '''
77         Application.name = name
78         try:
79             osrf.log.log_info("Loading application module %s" % module_name)
80             exec('import %s' % module_name)
81         except Exception, e:
82             osrf.log.log_error("Error importing application module %s:%s" % (
83                 module_name, unicode(e)))
84
85     @staticmethod
86     def register_app(app):
87         ''' Registers an application for use '''
88         app.name = Application.name
89         Application.application = app
90
91     @staticmethod
92     def register_method(**kwargs):
93         Application.methods[kwargs['api_name']] = Method(**kwargs)
94         if kwargs.get('stream'):
95             kwargs['atomic'] = 1 
96             kwargs['api_name'] +=  '.atomic'
97             Application.methods[kwargs['api_name']] = Method(**kwargs)
98
99
100     @staticmethod
101     def handle_request(session, osrf_msg):
102         ''' Find the handler, construct the server request, then run the method '''
103
104         req_method = osrf_msg.payload()
105         params = req_method.params()
106         method = Application.methods[req_method.method()]
107         handler = method.get_func()
108
109         param_json = osrf.json.to_json(params)
110         param_json = param_json[1:len(param_json)-1]
111
112         osrf.log.log_info("CALL: %s %s %s" % (session.service, method.name, param_json))
113         server_req = osrf.ses.ServerRequest(session, osrf_msg.threadTrace(), method, params)
114
115         result = None
116         try:
117             result = handler(server_req, *params)
118         except Exception, e:
119             osrf.log.log_error("Error running method %s %s %s" % (method.name, param_json, unicode(e)))
120             session.send_status(
121                 osrf_msg.threadTrace(),
122                 osrf.net_obj.NetworkObject.osrfMethodException({   
123                     'status' : unicode(e),
124                     'statusCode': osrf.const.OSRF_STATUS_INTERNALSERVERERROR
125                 })
126             )
127             return
128
129         server_req.respond_complete(result)
130
131     @staticmethod
132     def register_sysmethods():
133         ''' Registers the global system methods '''
134
135         Application.register_method(
136             api_name = 'opensrf.system.time',
137             method = 'sysmethod_time',
138             argc = 0,
139         )
140         
141         Application.register_method(
142             api_name = 'opensrf.system.introspect',
143             method = 'sysmethod_introspect',
144             argc = 0,
145             stream = True
146         )
147
148
149     def sysmethod_time(self, request):
150         '''@return type:number The current epoch time '''
151         return time.time()
152
153     def sysmethod_introspect(self, request, prefix=None):
154         ''' Generates a list of methods with method metadata 
155             @param type:string The limiting method name prefix.  If defined,
156             only methods matching the given prefix will be returned.
157             @return type:array List of method information '''
158
159         for name, method in self.methods.iteritems():
160             if prefix is not None and prefix != name[:len(prefix)]:
161                 continue
162
163             request.respond({
164                 'api_name' : name,
165                 'method' : method.handler,
166                 'service' : self.name,
167                 'argc' : method.argc,
168                 'params' : [], # XXX parse me
169                 'desc' : method.get_doc() # XXX parse me
170
171             })
172
173