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