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 socket
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
35# TODO(akeshet): Eliminate this and replace with monarch metrics. (At the
36# moment, I don't think we can just easily swap out, because this module is
37# called by apache for rpc handling, and we don't have a ts_mon thread for that
38# yet).
39from autotest_lib.client.common_lib.cros.graphite import autotest_stats
40
41
42json_decoder = decoder.JSONDecoder()
43
44
45def customConvertJson(value):
46    """\
47    Recursively process JSON values and do type conversions.
48    -change floats to ints
49    -change unicodes to strs
50    """
51    if isinstance(value, float):
52        return int(value)
53    elif isinstance(value, unicode):
54        return str(value)
55    elif isinstance(value, list):
56        return [customConvertJson(item) for item in value]
57    elif isinstance(value, dict):
58        new_dict = {}
59        for key, val in value.iteritems():
60            new_key = customConvertJson(key)
61            new_val = customConvertJson(val)
62            new_dict[new_key] = new_val
63        return new_dict
64    else:
65        return value
66
67
68def ServiceMethod(fn):
69    fn.IsServiceMethod = True
70    return fn
71
72class ServiceException(Exception):
73    pass
74
75class ServiceRequestNotTranslatable(ServiceException):
76    pass
77
78class BadServiceRequest(ServiceException):
79    pass
80
81class ServiceMethodNotFound(ServiceException):
82    pass
83
84
85class ServiceHandler(object):
86
87    def __init__(self, service):
88        self.service=service
89
90
91    @classmethod
92    def blank_result_dict(cls):
93        return {'id': None, 'result': None, 'err': None, 'err_traceback': None}
94
95    def dispatchRequest(self, request):
96        """
97        Invoke a json RPC call from a decoded json request.
98        @param request: a decoded json_request
99        @returns a dictionary with keys id, result, err and err_traceback
100        """
101        results = self.blank_result_dict()
102
103        try:
104            results['id'] = self._getRequestId(request)
105            methName = request['method']
106            args = request['params']
107        except KeyError:
108            raise BadServiceRequest(request)
109
110        autotest_stats.Counter('rpc').increment(methName)
111
112        metadata = request.copy()
113        metadata['_type'] = 'rpc'
114        metadata['rpc_server'] = socket.gethostname()
115        timer = autotest_stats.Timer('rpc', metadata=metadata)
116
117        try:
118            timer.start()
119            meth = self.findServiceEndpoint(methName)
120            results['result'] = self.invokeServiceEndpoint(meth, args)
121        except Exception, err:
122            results['err_traceback'] = traceback.format_exc()
123            results['err'] = err
124        finally:
125            timer.stop(methName)
126
127        return results
128
129
130    def _getRequestId(self, request):
131        try:
132            return request['id']
133        except KeyError:
134            raise BadServiceRequest(request)
135
136
137    def handleRequest(self, jsonRequest):
138        request = self.translateRequest(jsonRequest)
139        results = self.dispatchRequest(request)
140        return self.translateResult(results)
141
142
143    @staticmethod
144    def translateRequest(data):
145        try:
146            req = json_decoder.decode(data)
147        except:
148            raise ServiceRequestNotTranslatable(data)
149        req = customConvertJson(req)
150        return req
151
152    def findServiceEndpoint(self, name):
153        try:
154            meth = getattr(self.service, name)
155            return meth
156        except AttributeError:
157            raise ServiceMethodNotFound(name)
158
159    def invokeServiceEndpoint(self, meth, args):
160        return meth(*args)
161
162    @staticmethod
163    def translateResult(result_dict):
164        """
165        @param result_dict: a dictionary containing the result, error, traceback
166                            and id.
167        @returns translated json result
168        """
169        if result_dict['err'] is not None:
170            error_name = result_dict['err'].__class__.__name__
171            result_dict['err'] = {'name': error_name,
172                                  'message': str(result_dict['err']),
173                                  'traceback': result_dict['err_traceback']}
174            result_dict['result'] = None
175
176        try:
177            json_dict = {'result': result_dict['result'],
178                         'id': result_dict['id'],
179                         'error': result_dict['err'] }
180            data = json_encoder.encode(json_dict)
181        except TypeError, e:
182            err_traceback = traceback.format_exc()
183            print err_traceback
184            err = {"name" : "JSONEncodeException",
185                   "message" : "Result Object Not Serializable",
186                   "traceback" : err_traceback}
187            data = json_encoder.encode({"result":None, "id":result_dict['id'],
188                                        "error":err})
189
190        return data
191