server/hooksmanager.py
changeset 0 b97547f5f1fa
child 237 3df2e0ae2eba
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/hooksmanager.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,254 @@
+"""Hooks management
+
+Hooks are called before / after any individual update of entities / relations
+in the repository.
+
+Here is the prototype of the different hooks:
+
+* filtered on the entity's type:
+
+  before_add_entity    (session, entity)
+  after_add_entity     (session, entity)
+  before_update_entity (session, entity)
+  after_update_entity  (session, entity)
+  before_delete_entity (session, eid)
+  after_delete_entity  (session, eid)
+
+* filtered on the relation's type:
+
+  before_add_relation    (session, fromeid, rtype, toeid)
+  after_add_relation     (session, fromeid, rtype, toeid)
+  before_delete_relation (session, fromeid, rtype, toeid)
+  after_delete_relation  (session, fromeid, rtype, toeid)
+
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+ENTITIES_HOOKS = ('before_add_entity',    'after_add_entity', 
+                  'before_update_entity', 'after_update_entity',
+                  'before_delete_entity', 'after_delete_entity')
+RELATIONS_HOOKS = ('before_add_relation',   'after_add_relation' ,
+                   'before_delete_relation','after_delete_relation')
+SYSTEM_HOOKS = ('server_startup', 'server_shutdown',
+                'session_open', 'session_close')
+
+ALL_HOOKS = frozenset(ENTITIES_HOOKS + RELATIONS_HOOKS + SYSTEM_HOOKS)
+
+class HooksManager(object):
+    """handle hooks registration and calls
+    """
+    verification_hooks_activated = True
+    
+    def __init__(self, schema):
+        self.set_schema(schema)
+
+    def set_schema(self, schema):
+        self._hooks = {}
+        self.schema = schema
+        self._init_hooks(schema)
+        
+    def register_hooks(self, hooks):
+        """register a dictionary of hooks :
+        
+             {'event': {'entity or relation type': [callbacks list]}}
+        """
+        for event, subevents in hooks.items():
+            for subevent, callbacks in subevents.items():
+                for callback in callbacks:
+                    self.register_hook(callback, event, subevent)
+                    
+    def register_hook(self, function, event, etype=''):
+        """register a function to call when <event> occurs
+        
+         <etype> is an entity/relation type or an empty string.
+         If etype is the empty string, the function will be called at each
+         event, else the function will be called only when event occurs on an
+         entity/relation of the given type.
+        """
+        assert event in ALL_HOOKS, '%r NOT IN %r' % (event, ALL_HOOKS)
+        assert (not event in SYSTEM_HOOKS or not etype), (event, etype)
+        etype = etype or ''
+        try:
+            self._hooks[event][etype].append(function)
+            self.debug('registered hook %s on %s (%s)', event, etype or 'any',
+                       function.func_name)
+            
+        except KeyError:
+            self.error('can\'t register hook %s on %s (%s)',
+                       event, etype or 'any', function.func_name)
+            
+    def unregister_hook(self, function, event, etype=''):
+        """register a function to call when <event> occurs
+        
+        <etype> is an entity/relation type or an empty string.
+        If etype is the empty string, the function will be called at each
+        event, else the function will be called only when event occurs on an
+        entity/relation of the given type.
+        """
+        assert event in ALL_HOOKS, event
+        etype = etype or ''
+        self.info('unregister hook %s on %s (%s)', event, etype,
+                  function.func_name)
+        self._hooks[event][etype].remove(function)
+
+    def call_hooks(self, __event, __type='', *args, **kwargs):
+        """call hook matching event and optional type"""
+        if __type:
+            self.info('calling hooks for event %s (%s)', __event, __type)
+        else:
+            self.info('calling hooks for event %s', __event)
+        # call generic hooks first
+        for hook in self._hooks[__event]['']:
+            #print '[generic]', hook.__name__
+            hook(*args, **kwargs)
+        if __type:
+            for hook in self._hooks[__event][__type]:
+                #print '[%s]'%__type, hook.__name__
+                hook(*args, **kwargs)
+    
+    def _init_hooks(self, schema):
+        """initialize the hooks map"""
+        for hook_event in ENTITIES_HOOKS:
+            self._hooks[hook_event] = {'': []}
+            for etype in schema.entities():
+                self._hooks[hook_event][etype] = []
+        for hook_event in RELATIONS_HOOKS:
+            self._hooks[hook_event] = {'': []}
+            for r_type in schema.relations():
+                self._hooks[hook_event][r_type] = []
+        for hook_event in SYSTEM_HOOKS:
+            self._hooks[hook_event] = {'': []}
+
+    def register_system_hooks(self, config):
+        """register system hooks according to the configuration"""
+        self.info('register core hooks')
+        from cubicweb.server.hooks import _register_metadata_hooks, _register_wf_hooks
+        _register_metadata_hooks(self)
+        self.info('register workflow hooks')
+        _register_wf_hooks(self)
+        if config.core_hooks:
+            from cubicweb.server.hooks import _register_core_hooks
+            _register_core_hooks(self)
+        if config.schema_hooks:
+            from cubicweb.server.schemahooks import _register_schema_hooks
+            self.info('register schema hooks')
+            _register_schema_hooks(self)
+        if config.usergroup_hooks:
+            from cubicweb.server.hooks import _register_usergroup_hooks
+            from cubicweb.server.hooks import _register_eproperty_hooks
+            self.info('register user/group hooks')
+            _register_usergroup_hooks(self)
+            _register_eproperty_hooks(self)
+        if config.security_hooks:
+            from cubicweb.server.securityhooks import register_security_hooks
+            self.info('register security hooks')
+            register_security_hooks(self)
+        if not self.verification_hooks_activated:
+            self.deactivate_verification_hooks()
+
+    def deactivate_verification_hooks(self):
+        from cubicweb.server.hooks import (cardinalitycheck_after_add_entity,
+                                        cardinalitycheck_before_del_relation,
+                                        cstrcheck_after_add_relation,
+                                        uniquecstrcheck_before_modification)
+        self.warning('deactivating verification hooks')
+        self.verification_hooks_activated = False
+        self.unregister_hook(cardinalitycheck_after_add_entity, 'after_add_entity', '')
+        self.unregister_hook(cardinalitycheck_before_del_relation, 'before_delete_relation', '')
+        self.unregister_hook(cstrcheck_after_add_relation, 'after_add_relation', '')
+        self.unregister_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
+        self.unregister_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
+#         self.unregister_hook(tidy_html_fields('before_add_entity'), 'before_add_entity', '')
+#         self.unregister_hook(tidy_html_fields('before_update_entity'), 'before_update_entity', '')
+        
+    def reactivate_verification_hooks(self):
+        from cubicweb.server.hooks import (cardinalitycheck_after_add_entity,
+                                        cardinalitycheck_before_del_relation,
+                                        cstrcheck_after_add_relation,
+                                        uniquecstrcheck_before_modification)
+        self.warning('reactivating verification hooks')
+        self.verification_hooks_activated = True
+        self.register_hook(cardinalitycheck_after_add_entity, 'after_add_entity', '')
+        self.register_hook(cardinalitycheck_before_del_relation, 'before_delete_relation', '')
+        self.register_hook(cstrcheck_after_add_relation, 'after_add_relation', '')
+        self.register_hook(uniquecstrcheck_before_modification, 'before_add_entity', '')
+        self.register_hook(uniquecstrcheck_before_modification, 'before_update_entity', '')
+#         self.register_hook(tidy_html_fields('before_add_entity'), 'before_add_entity', '')
+#         self.register_hook(tidy_html_fields('before_update_entity'), 'before_update_entity', '')
+            
+from cubicweb.vregistry import autoselectors
+from cubicweb.common.appobject import AppObject
+from cubicweb.common.registerers import accepts_registerer, yes_registerer
+from cubicweb.common.selectors import yes_selector
+
+class autoid(autoselectors):
+    """metaclass to create an unique 'id' attribute on the class using it"""
+    def __new__(mcs, name, bases, classdict):
+        cls = super(autoid, mcs).__new__(mcs, name, bases, classdict)
+        cls.id = str(id(cls))
+        return cls
+
+class Hook(AppObject):
+    __metaclass__ = autoid
+    __registry__ = 'hooks'
+    __registerer__ = accepts_registerer
+    __selectors__ = (yes_selector,)
+    # set this in derivated classes
+    events = None
+    accepts = None
+    enabled = True
+    
+    def __init__(self, event=None):
+        super(Hook, self).__init__()
+        self.event = event
+        
+    @classmethod
+    def registered(cls, vreg):
+        super(Hook, cls).registered(vreg)
+        return cls()
+    
+    @classmethod
+    def register_to(cls):
+        if not cls.enabled:
+            cls.warning('%s hook has been disabled', cls)
+            return
+        done = set()
+        for event in cls.events:
+            for ertype in cls.accepts:
+                if (event, ertype) in done:
+                    continue
+                yield event, ertype
+                done.add((event, ertype))
+                try:
+                    eschema = cls.schema.eschema(ertype)
+                except KeyError:
+                    # relation schema
+                    pass
+                else:
+                    for eetype in eschema.specialized_by():
+                        if (event, eetype) in done:
+                            continue
+                        yield event, str(eetype)
+                        done.add((event, eetype))
+                        
+
+    def make_callback(self, event):
+        if len(self.events) == 1:
+            return self.call
+        return self.__class__(event=event).call
+
+    def call(self):
+        raise NotImplementedError
+    
+class SystemHook(Hook):
+    __registerer__ = yes_registerer
+    accepts = ('',)
+
+from logging import getLogger
+from cubicweb import set_log_methods
+set_log_methods(HooksManager, getLogger('cubicweb.hooksmanager'))
+set_log_methods(Hook, getLogger('cubicweb.hooks'))