Package Products :: Package ZenUtils :: Package extdirect :: Module router
[hide private]
[frames] | no frames]

Source Code for Module Products.ZenUtils.extdirect.router

  1  ############################################################################## 
  2  #  
  3  # Copyright (C) Zenoss, Inc. 2009, all rights reserved. 
  4  #  
  5  # This content is made available according to terms specified in 
  6  # License.zenoss under the directory where your Zenoss product is installed. 
  7  #  
  8  ############################################################################## 
  9   
 10   
 11  import inspect 
 12  import logging 
 13  from Products.ZenUtils.jsonutils import json, unjson 
 14  import transaction 
 15  from uuid import uuid4 
 16   
 17  log = logging.getLogger('extdirect') 
18 19 -class DirectException(Exception):
20 pass
21
22 23 24 -class DirectResponse(object):
25 """ 26 Encapsulation of the simple protocol used to send results and messages to 27 the front end. 28 """ 29 _data = None
30 - def __init__(self, msg=None, success=True, **kwargs):
31 self._data = {} 32 self._data.update(kwargs) 33 self._data['success'] = success 34 if msg: 35 self._data['msg'] = msg
36 37 @property
38 - def type(self):
39 return self._data.get('type', None)
40 41 @property
42 - def data(self):
43 return self._data
44
45 - def __setitem__(self, key, value):
46 self._data[key] = value
47 48 @staticmethod
49 - def exception(exception, message=None, **kwargs):
50 """ 51 If an exception is raised, use this! It will cause the transaction to be rolled 52 back. 53 """ 54 msg = exception.__class__.__name__ + ': ' + str(exception) 55 if message: 56 msg = '%s: %s' % (message, msg) 57 return DirectResponse(msg, success=False, type='exception', **kwargs)
58 59 @staticmethod
60 - def fail(msg=None, **kwargs):
61 """ 62 If the request has failed for a non-database-impacting reason 63 (e.g., "device already exists"), use this. 64 """ 65 return DirectResponse(msg, success=False, type='error', **kwargs)
66 67 @staticmethod
68 - def succeed(msg=None, **kwargs):
69 return DirectResponse(msg, success=True, **kwargs)
70
71 - def __json__(self):
72 return self.data
73
74 -class DirectMethodResponse(object):
75 """ 76 Encapsulation of the response of a method call for Ext Direct. 77 """
78 - def __init__(self, tid, action, method, uuid):
79 self.tid = tid 80 self.type = 'rpc' 81 self.action = action 82 self.method = method 83 self.uuid = uuid 84 self.result = None
85
86 - def __json__(self):
87 return { 88 'tid': self.tid, 89 'type' : self.type, 90 'action': self.action, 91 'method': self.method, 92 'uuid': self.uuid, 93 'result': self.result 94 }
95
96 -class DirectRouter(object):
97 """ 98 Basic Ext.Direct router class. 99 100 Ext.Direct allows one to create an API that communicates with a single URL, 101 which then routes requests to the appropriate method. The client-side API 102 object matches the server-side API object. 103 104 This base class parses an Ext.Direct request, which contains the name of 105 the method and any data that should be passed, and routes the data to the 106 approriate method. It then receives the output of that call and puts it 107 into the data structure expected by Ext.Direct. 108 109 Call an instance of this class with the JSON from an Ext.Direct request. 110 """ 111 112 @json
113 - def __call__(self, body):
114 # Decode the request data 115 try: 116 body = unjson(body) 117 except Exception: 118 raise DirectException("Request body is not unjson()-able: %s" % body) 119 self._body = body 120 121 if isinstance(body, list): 122 directRequests = body 123 elif isinstance(body, dict): 124 directRequests = [body] 125 else: 126 raise DirectException("Body is not a supported type: %s" % body) 127 128 directResponses = [] 129 for directRequest in directRequests: 130 directResponses.append(self._processDirectRequest(directRequest)) 131 132 if len(directResponses) == 1: 133 directResponses = directResponses[0] 134 135 return directResponses
136
137 - def _processDirectRequest(self, directRequest):
138 savepoint = transaction.savepoint() 139 140 # Add a UUID so we can track it in the logs 141 uuid = str(uuid4()) 142 143 # Double-check that this request is meant for this class 144 action = directRequest.get('action') 145 clsname = self.__class__.__name__ 146 if action != clsname: 147 raise DirectException(("Action specified in request ('%s') is" 148 " not named %s.") % (action, clsname)) 149 150 # Pull out the method name and make sure it exists on this class 151 method = directRequest.get('method') 152 if not method: 153 raise DirectException("No method specified. Is this a valid" 154 " Ext.Direct request?") 155 try: 156 _targetfn = getattr(self, method) 157 except AttributeError: 158 raise DirectException("'%s' is not the name of a method on %s" % ( 159 method, clsname 160 )) 161 162 # Pull out any arguments. Sent as an array containing a hash map, so 163 # get the first member. 164 data = directRequest.get('data') 165 if not data: 166 data = {} 167 else: 168 data = data[0] 169 170 if isinstance(data, (int, basestring)): 171 data = {'id': data} 172 173 # Cast all keys as strings, in case of encoding or other wrinkles 174 data = dict((str(k), v) for k,v in data.iteritems()) 175 self._data = data 176 response = DirectMethodResponse(tid=directRequest['tid'], method=method, action=action, uuid=uuid) 177 178 # Finally, call the target method, passing in the data 179 try: 180 response.result = _targetfn(**data) 181 except Exception as e: 182 import traceback 183 log.info("Direct request failed: {0}: {1[action]}.{1[method]} {1[data]}".format(e, directRequest)) 184 log.info("DirectRouter suppressed the following exception (Response {0}):\n{1}".format(response.uuid, traceback.format_exc())) 185 response.result = DirectResponse.exception(e) 186 187 if isinstance(response.result, DirectResponse) and response.result.type == 'exception': 188 savepoint.rollback() 189 190 return response
191
192 193 -class DirectProviderDefinition(object):
194 """ 195 Turns a L{DirectRouter} subclass into JavaScript object representing the 196 config of the client-side API. 197 198 Inspects the given subclass and retrieves the names of all public methods, 199 then defines those as actions on the Ext.Direct provider, and creates the 200 JS that adds the provider. 201 202 See http://www.sencha.com/products/extjs/extdirect for a full explanation of 203 protocols and features of Ext.Direct. 204 """
205 - def __init__(self, routercls, url, timeout, ns=None):
206 """ 207 @param routercls: A L{DirectRouter} subclass 208 @type routercls: class 209 @param url: The url at which C{routercls} is available 210 @type url: str 211 @param ns: The client-side namespace in which the provider should live. 212 The provider will be available at [ns].[routercls.__name__]. 213 For example, if ns is 'Zenoss.remote' and routercls is named 214 'EventConsole', client-side code would call 215 C{Zenoss.remote.EventConsole.my_method(params, callback)}. 216 """ 217 self.routercls = routercls 218 self.url = url 219 self.ns = ns 220 self.timeout = timeout
221
222 - def _gen_public_methods(self):
223 for name, value in inspect.getmembers(self.routercls): 224 if name.startswith("_"): 225 continue 226 if inspect.ismethod(value): 227 yield name, value
228
229 - def _gen_method_infos(self, strategy):
230 for name, method in self._gen_public_methods(): 231 method_info = {'name': name} 232 strategy(method_info, method) 233 yield method_info
234
235 - def _extdirect_config(self):
236 def strategy(method_info, method): 237 method_info["len"] = 1
238 method_infos = self._gen_method_infos(strategy) 239 config = { 240 'id': self.routercls.__name__, 241 'type': 'remoting', 242 'url': self.url, 243 'timeout': self.timeout, 244 'enableBuffer': 100, 245 'actions': { 246 self.routercls.__name__: list(method_infos) 247 } 248 } 249 if self.ns: 250 config['namespace'] = self.ns 251 return config
252
253 - def _jsonapi_config(self):
254 def strategy(method_info, method): 255 method_info['args'] = inspect.formatargspec(*inspect.getargspec(method)) 256 method_info['doc'] = method.__doc__
257 method_infos = self._gen_method_infos(strategy) 258 config = {"action": self.routercls.__name__, 259 "url": self.url, 260 "methods": list(method_infos), 261 } 262 return config 263
264 - def render(self):
265 """ 266 Generate and return an Ext.Direct provider definition, wrapped in a 267 <script> tag and ready for inclusion in an HTML document. 268 """ 269 config = self._extdirect_config() 270 source = "\nExt.Direct.addProvider(%s);\n" % json(config) 271 return source.strip()
272
273 - def render_jsonapi(self):
274 """ 275 Generate an Ext.Direct provider definition for the python client 276 """ 277 config = self._jsonapi_config() 278 source = json(config) 279 return source
280