server/hooksmanager.py
changeset 0 b97547f5f1fa
child 237 3df2e0ae2eba
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """Hooks management
       
     2 
       
     3 Hooks are called before / after any individual update of entities / relations
       
     4 in the repository.
       
     5 
       
     6 Here is the prototype of the different hooks:
       
     7 
       
     8 * filtered on the entity's type:
       
     9 
       
    10   before_add_entity    (session, entity)
       
    11   after_add_entity     (session, entity)
       
    12   before_update_entity (session, entity)
       
    13   after_update_entity  (session, entity)
       
    14   before_delete_entity (session, eid)
       
    15   after_delete_entity  (session, eid)
       
    16 
       
    17 * filtered on the relation's type:
       
    18 
       
    19   before_add_relation    (session, fromeid, rtype, toeid)
       
    20   after_add_relation     (session, fromeid, rtype, toeid)
       
    21   before_delete_relation (session, fromeid, rtype, toeid)
       
    22   after_delete_relation  (session, fromeid, rtype, toeid)
       
    23 
       
    24 
       
    25 :organization: Logilab
       
    26 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
    27 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
    28 """
       
    29 __docformat__ = "restructuredtext en"
       
    30 
       
    31 ENTITIES_HOOKS = ('before_add_entity',    'after_add_entity', 
       
    32                   'before_update_entity', 'after_update_entity',
       
    33                   'before_delete_entity', 'after_delete_entity')
       
    34 RELATIONS_HOOKS = ('before_add_relation',   'after_add_relation' ,
       
    35                    'before_delete_relation','after_delete_relation')
       
    36 SYSTEM_HOOKS = ('server_startup', 'server_shutdown',
       
    37                 'session_open', 'session_close')
       
    38 
       
    39 ALL_HOOKS = frozenset(ENTITIES_HOOKS + RELATIONS_HOOKS + SYSTEM_HOOKS)
       
    40 
       
    41 class HooksManager(object):
       
    42     """handle hooks registration and calls
       
    43     """
       
    44     verification_hooks_activated = True
       
    45     
       
    46     def __init__(self, schema):
       
    47         self.set_schema(schema)
       
    48 
       
    49     def set_schema(self, schema):
       
    50         self._hooks = {}
       
    51         self.schema = schema
       
    52         self._init_hooks(schema)
       
    53         
       
    54     def register_hooks(self, hooks):
       
    55         """register a dictionary of hooks :
       
    56         
       
    57              {'event': {'entity or relation type': [callbacks list]}}
       
    58         """
       
    59         for event, subevents in hooks.items():
       
    60             for subevent, callbacks in subevents.items():
       
    61                 for callback in callbacks:
       
    62                     self.register_hook(callback, event, subevent)
       
    63                     
       
    64     def register_hook(self, function, event, etype=''):
       
    65         """register a function to call when <event> occurs
       
    66         
       
    67          <etype> is an entity/relation type or an empty string.
       
    68          If etype is the empty string, the function will be called at each
       
    69          event, else the function will be called only when event occurs on an
       
    70          entity/relation of the given type.
       
    71         """
       
    72         assert event in ALL_HOOKS, '%r NOT IN %r' % (event, ALL_HOOKS)
       
    73         assert (not event in SYSTEM_HOOKS or not etype), (event, etype)
       
    74         etype = etype or ''
       
    75         try:
       
    76             self._hooks[event][etype].append(function)
       
    77             self.debug('registered hook %s on %s (%s)', event, etype or 'any',
       
    78                        function.func_name)
       
    79             
       
    80         except KeyError:
       
    81             self.error('can\'t register hook %s on %s (%s)',
       
    82                        event, etype or 'any', function.func_name)
       
    83             
       
    84     def unregister_hook(self, function, event, etype=''):
       
    85         """register a function to call when <event> occurs
       
    86         
       
    87         <etype> is an entity/relation type or an empty string.
       
    88         If etype is the empty string, the function will be called at each
       
    89         event, else the function will be called only when event occurs on an
       
    90         entity/relation of the given type.
       
    91         """
       
    92         assert event in ALL_HOOKS, event
       
    93         etype = etype or ''
       
    94         self.info('unregister hook %s on %s (%s)', event, etype,
       
    95                   function.func_name)
       
    96         self._hooks[event][etype].remove(function)
       
    97 
       
    98     def call_hooks(self, __event, __type='', *args, **kwargs):
       
    99         """call hook matching event and optional type"""
       
   100         if __type:
       
   101             self.info('calling hooks for event %s (%s)', __event, __type)
       
   102         else:
       
   103             self.info('calling hooks for event %s', __event)
       
   104         # call generic hooks first
       
   105         for hook in self._hooks[__event]['']:
       
   106             #print '[generic]', hook.__name__
       
   107             hook(*args, **kwargs)
       
   108         if __type:
       
   109             for hook in self._hooks[__event][__type]:
       
   110                 #print '[%s]'%__type, hook.__name__
       
   111                 hook(*args, **kwargs)
       
   112     
       
   113     def _init_hooks(self, schema):
       
   114         """initialize the hooks map"""
       
   115         for hook_event in ENTITIES_HOOKS:
       
   116             self._hooks[hook_event] = {'': []}
       
   117             for etype in schema.entities():
       
   118                 self._hooks[hook_event][etype] = []
       
   119         for hook_event in RELATIONS_HOOKS:
       
   120             self._hooks[hook_event] = {'': []}
       
   121             for r_type in schema.relations():
       
   122                 self._hooks[hook_event][r_type] = []
       
   123         for hook_event in SYSTEM_HOOKS:
       
   124             self._hooks[hook_event] = {'': []}
       
   125 
       
   126     def register_system_hooks(self, config):
       
   127         """register system hooks according to the configuration"""
       
   128         self.info('register core hooks')
       
   129         from cubicweb.server.hooks import _register_metadata_hooks, _register_wf_hooks
       
   130         _register_metadata_hooks(self)
       
   131         self.info('register workflow hooks')
       
   132         _register_wf_hooks(self)
       
   133         if config.core_hooks:
       
   134             from cubicweb.server.hooks import _register_core_hooks
       
   135             _register_core_hooks(self)
       
   136         if config.schema_hooks:
       
   137             from cubicweb.server.schemahooks import _register_schema_hooks
       
   138             self.info('register schema hooks')
       
   139             _register_schema_hooks(self)
       
   140         if config.usergroup_hooks:
       
   141             from cubicweb.server.hooks import _register_usergroup_hooks
       
   142             from cubicweb.server.hooks import _register_eproperty_hooks
       
   143             self.info('register user/group hooks')
       
   144             _register_usergroup_hooks(self)
       
   145             _register_eproperty_hooks(self)
       
   146         if config.security_hooks:
       
   147             from cubicweb.server.securityhooks import register_security_hooks
       
   148             self.info('register security hooks')
       
   149             register_security_hooks(self)
       
   150         if not self.verification_hooks_activated:
       
   151             self.deactivate_verification_hooks()
       
   152 
       
   153     def deactivate_verification_hooks(self):
       
   154         from cubicweb.server.hooks import (cardinalitycheck_after_add_entity,
       
   155                                         cardinalitycheck_before_del_relation,
       
   156                                         cstrcheck_after_add_relation,
       
   157                                         uniquecstrcheck_before_modification)
       
   158         self.warning('deactivating verification hooks')
       
   159         self.verification_hooks_activated = False
       
   160         self.unregister_hook(cardinalitycheck_after_add_entity, 'after_add_entity', '')
       
   161         self.unregister_hook(cardinalitycheck_before_del_relation, 'before_delete_relation', '')
       
   162         self.unregister_hook(cstrcheck_after_add_relation, 'after_add_relation', '')
       
   163         self.unregister_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
       
   164         self.unregister_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
       
   165 #         self.unregister_hook(tidy_html_fields('before_add_entity'), 'before_add_entity', '')
       
   166 #         self.unregister_hook(tidy_html_fields('before_update_entity'), 'before_update_entity', '')
       
   167         
       
   168     def reactivate_verification_hooks(self):
       
   169         from cubicweb.server.hooks import (cardinalitycheck_after_add_entity,
       
   170                                         cardinalitycheck_before_del_relation,
       
   171                                         cstrcheck_after_add_relation,
       
   172                                         uniquecstrcheck_before_modification)
       
   173         self.warning('reactivating verification hooks')
       
   174         self.verification_hooks_activated = True
       
   175         self.register_hook(cardinalitycheck_after_add_entity, 'after_add_entity', '')
       
   176         self.register_hook(cardinalitycheck_before_del_relation, 'before_delete_relation', '')
       
   177         self.register_hook(cstrcheck_after_add_relation, 'after_add_relation', '')
       
   178         self.register_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
       
   179         self.register_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
       
   180 #         self.register_hook(tidy_html_fields('before_add_entity'), 'before_add_entity', '')
       
   181 #         self.register_hook(tidy_html_fields('before_update_entity'), 'before_update_entity', '')
       
   182             
       
   183 from cubicweb.vregistry import autoselectors
       
   184 from cubicweb.common.appobject import AppObject
       
   185 from cubicweb.common.registerers import accepts_registerer, yes_registerer
       
   186 from cubicweb.common.selectors import yes_selector
       
   187 
       
   188 class autoid(autoselectors):
       
   189     """metaclass to create an unique 'id' attribute on the class using it"""
       
   190     def __new__(mcs, name, bases, classdict):
       
   191         cls = super(autoid, mcs).__new__(mcs, name, bases, classdict)
       
   192         cls.id = str(id(cls))
       
   193         return cls
       
   194 
       
   195 class Hook(AppObject):
       
   196     __metaclass__ = autoid
       
   197     __registry__ = 'hooks'
       
   198     __registerer__ = accepts_registerer
       
   199     __selectors__ = (yes_selector,)
       
   200     # set this in derivated classes
       
   201     events = None
       
   202     accepts = None
       
   203     enabled = True
       
   204     
       
   205     def __init__(self, event=None):
       
   206         super(Hook, self).__init__()
       
   207         self.event = event
       
   208         
       
   209     @classmethod
       
   210     def registered(cls, vreg):
       
   211         super(Hook, cls).registered(vreg)
       
   212         return cls()
       
   213     
       
   214     @classmethod
       
   215     def register_to(cls):
       
   216         if not cls.enabled:
       
   217             cls.warning('%s hook has been disabled', cls)
       
   218             return
       
   219         done = set()
       
   220         for event in cls.events:
       
   221             for ertype in cls.accepts:
       
   222                 if (event, ertype) in done:
       
   223                     continue
       
   224                 yield event, ertype
       
   225                 done.add((event, ertype))
       
   226                 try:
       
   227                     eschema = cls.schema.eschema(ertype)
       
   228                 except KeyError:
       
   229                     # relation schema
       
   230                     pass
       
   231                 else:
       
   232                     for eetype in eschema.specialized_by():
       
   233                         if (event, eetype) in done:
       
   234                             continue
       
   235                         yield event, str(eetype)
       
   236                         done.add((event, eetype))
       
   237                         
       
   238 
       
   239     def make_callback(self, event):
       
   240         if len(self.events) == 1:
       
   241             return self.call
       
   242         return self.__class__(event=event).call
       
   243 
       
   244     def call(self):
       
   245         raise NotImplementedError
       
   246     
       
   247 class SystemHook(Hook):
       
   248     __registerer__ = yes_registerer
       
   249     accepts = ('',)
       
   250 
       
   251 from logging import getLogger
       
   252 from cubicweb import set_log_methods
       
   253 set_log_methods(HooksManager, getLogger('cubicweb.hooksmanager'))
       
   254 set_log_methods(Hook, getLogger('cubicweb.hooks'))