serviceHandler.py revision 59cfe5497d7f3be2bc4ade144d649a131e88448e
1
2"""
3  Copyright (c) 2007 Jan-Klaas Kollhof
4
5  This file is part of jsonrpc.
6
7  jsonrpc is free software; you can redistribute it and/or modify
8  it under the terms of the GNU Lesser General Public License as published by
9  the Free Software Foundation; either version 2.1 of the License, or
10  (at your option) any later version.
11
12  This software is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  GNU Lesser General Public License for more details.
16
17  You should have received a copy of the GNU Lesser General Public License
18  along with this software; if not, write to the Free Software
19  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20"""
21
22import traceback
23
24from json import decoder
25
26try:
27    from django.core import exceptions as django_exceptions
28    # Django JSON encoder uses the standard json encoder but can handle DateTime
29    from django.core.serializers import json as django_encoder
30    json_encoder = django_encoder.DjangoJSONEncoder()
31except django_exceptions.ImproperlyConfigured:
32    from json import encoder
33    json_encoder = encoder.JSONEncoder()
34
35from autotest_lib.client.common_lib.cros.graphite import stats
36
37
38json_decoder = decoder.JSONDecoder()
39
40
41def customConvertJson(value):
42    """\
43    Recursively process JSON values and do type conversions.
44    -change floats to ints
45    -change unicodes to strs
46    """
47    if isinstance(value, float):
48        return int(value)
49    elif isinstance(value, unicode):
50        return str(value)
51    elif isinstance(value, list):
52        return [customConvertJson(item) for item in value]
53    elif isinstance(value, dict):
54        new_dict = {}
55        for key, val in value.iteritems():
56            new_key = customConvertJson(key)
57            new_val = customConvertJson(val)
58            new_dict[new_key] = new_val
59        return new_dict
60    else:
61        return value
62
63
64def ServiceMethod(fn):
65    fn.IsServiceMethod = True
66    return fn
67
68class ServiceException(Exception):
69    pass
70
71class ServiceRequestNotTranslatable(ServiceException):
72    pass
73
74class BadServiceRequest(ServiceException):
75    pass
76
77class ServiceMethodNotFound(ServiceException):
78    pass
79
80
81class ServiceHandler(object):
82
83    def __init__(self, service):
84        self.service=service
85
86
87    @classmethod
88    def blank_result_dict(cls):
89        return {'id': None, 'result': None, 'err': None, 'err_traceback': None}
90
91    def dispatchRequest(self, request):
92        """
93        Invoke a json RPC call from a decoded json request.
94        @param request: a decoded json_request
95        @returns a dictionary with keys id, result, err and err_traceback
96        """
97        results = self.blank_result_dict()
98
99        try:
100            results['id'] = self._getRequestId(request)
101            methName = request['method']
102            args = request['params']
103        except KeyError:
104            raise BadServiceRequest(request)
105
106        stats.Counter('rpc').increment(methName)
107        timer = stats.Timer('rpc')
108
109        try:
110            timer.start()
111            meth = self.findServiceEndpoint(methName)
112            results['result'] = self.invokeServiceEndpoint(meth, args)
113        except Exception, err:
114            results['err_traceback'] = traceback.format_exc()
115            results['err'] = err
116        finally:
117            timer.stop(methName)
118
119        return results
120
121
122    def _getRequestId(self, request):
123        try:
124            return request['id']
125        except KeyError:
126            raise BadServiceRequest(request)
127
128
129    def handleRequest(self, jsonRequest):
130        request = self.translateRequest(jsonRequest)
131        results = self.dispatchRequest(request)
132        return self.translateResult(results)
133
134
135    @staticmethod
136    def translateRequest(data):
137        try:
138            req = json_decoder.decode(data)
139        except:
140            raise ServiceRequestNotTranslatable(data)
141        req = customConvertJson(req)
142        return req
143
144    def findServiceEndpoint(self, name):
145        try:
146            meth = getattr(self.service, name)
147            return meth
148        except AttributeError:
149            raise ServiceMethodNotFound(name)
150
151    def invokeServiceEndpoint(self, meth, args):
152        return meth(*args)
153
154    @staticmethod
155    def translateResult(result_dict):
156        """
157        @param result_dict: a dictionary containing the result, error, traceback
158                            and id.
159        @returns translated json result
160        """
161        if result_dict['err'] is not None:
162            error_name = result_dict['err'].__class__.__name__
163            result_dict['err'] = {'name': error_name,
164                                  'message': str(result_dict['err']),
165                                  'traceback': result_dict['err_traceback']}
166            result_dict['result'] = None
167
168        try:
169            json_dict = {'result': result_dict['result'],
170                         'id': result_dict['id'],
171                         'error': result_dict['err'] }
172            data = json_encoder.encode(json_dict)
173        except TypeError, e:
174            err_traceback = traceback.format_exc()
175            print err_traceback
176            err = {"name" : "JSONEncodeException",
177                   "message" : "Result Object Not Serializable",
178                   "traceback" : err_traceback}
179            data = json_encoder.encode({"result":None, "id":result_dict['id'],
180                                        "error":err})
181
182        return data
183