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