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