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

Source Code for Module Products.ZenUtils.extdirect.router

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