1
2
3
4
5
6
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')
21
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):
36
37 @property
40
41 @property
44
47
48 @staticmethod
49 - def exception(exception, message=None, **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
70
73
75 """
76 Encapsulation of the response of a method call for Ext Direct.
77 """
78 - def __init__(self, tid, action, method, uuid):
85
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
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
114
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
138 savepoint = transaction.savepoint()
139
140
141 uuid = str(uuid4())
142
143
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
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
163
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
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
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
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
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
230 for name, method in self._gen_public_methods():
231 method_info = {'name': name}
232 strategy(method_info, method)
233 yield method_info
234
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
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
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
280