"""Hooks managementHooks are called before / after any individual update of entities / relationsin 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-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses"""__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_backup','server_restore','server_startup','server_shutdown','session_open','session_close')ALL_HOOKS=frozenset(ENTITIES_HOOKS+RELATIONS_HOOKS+SYSTEM_HOOKS)classHooksManager(object):"""handle hooks registration and calls """verification_hooks_activated=Truedef__init__(self,schema):self.set_schema(schema)defset_schema(self,schema):self._hooks={}self.schema=schemaself._init_hooks(schema)defregister_hooks(self,hooks):"""register a dictionary of hooks : {'event': {'entity or relation type': [callbacks list]}} """forevent,subeventsinhooks.items():forsubevent,callbacksinsubevents.items():forcallbackincallbacks:self.register_hook(callback,event,subevent)defregister_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 or relation of the given type. """asserteventinALL_HOOKS,'%r NOT IN %r'%(event,ALL_HOOKS)assert(noteventinSYSTEM_HOOKSornotetype),(event,etype)etype=etypeor''try:self._hooks[event][etype].append(function)self.debug('registered hook %s on %s (%s)',event,etypeor'any',function.func_name)exceptKeyError:self.error('can\'t register hook %s on %s (%s)',event,etypeor'any',function.func_name)defunregister_hook(self,function_or_cls,event=None,etype=''):"""unregister a function to call when <event> occurs, or a Hook subclass. In the later case, event/type information are extracted from the given class. """ifisinstance(function_or_cls,type)andissubclass(function_or_cls,Hook):forevent,ertypeinfunction_or_cls.register_to():forhookinself._hooks[event][ertype]:ifgetattr(hook,'im_self',None).__class__isfunction_or_cls:self._hooks[event][ertype].remove(hook)self.info('unregister hook %s on %s (%s)',event,etype,function_or_cls.__name__)breakelse:self.warning("can't unregister hook %s on %s (%s), not found",event,etype,function_or_cls.__name__)else:asserteventinALL_HOOKS,eventetype=etypeor''self.info('unregister hook %s on %s (%s)',event,etype,function_or_cls.func_name)self._hooks[event][etype].remove(function_or_cls)defcall_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 firstforhookinself._hooks[__event]['']:#print '[generic]', hook.__name__hook(*args,**kwargs)if__type:forhookinself._hooks[__event][__type]:#print '[%s]'%__type, hook.__name__hook(*args,**kwargs)def_init_hooks(self,schema):"""initialize the hooks map"""forhook_eventinENTITIES_HOOKS:self._hooks[hook_event]={'':[]}foretypeinschema.entities():self._hooks[hook_event][etype]=[]forhook_eventinRELATIONS_HOOKS:self._hooks[hook_event]={'':[]}forr_typeinschema.relations():self._hooks[hook_event][r_type]=[]forhook_eventinSYSTEM_HOOKS:self._hooks[hook_event]={'':[]}defregister_system_hooks(self,config):"""register system hooks according to the configuration"""self.info('register core hooks')fromcubicweb.server.hooksimport_register_metadata_hooks,_register_wf_hooks_register_metadata_hooks(self)self.info('register workflow hooks')_register_wf_hooks(self)ifconfig.core_hooks:fromcubicweb.server.hooksimport_register_core_hooks_register_core_hooks(self)ifconfig.schema_hooks:fromcubicweb.server.schemahooksimport_register_schema_hooksself.info('register schema hooks')_register_schema_hooks(self)ifconfig.usergroup_hooks:fromcubicweb.server.hooksimport_register_usergroup_hooksfromcubicweb.server.hooksimport_register_eproperty_hooksself.info('register user/group hooks')_register_usergroup_hooks(self)_register_eproperty_hooks(self)ifconfig.security_hooks:fromcubicweb.server.securityhooksimportregister_security_hooksself.info('register security hooks')register_security_hooks(self)ifnotself.verification_hooks_activated:self.deactivate_verification_hooks()defdeactivate_verification_hooks(self):fromcubicweb.server.hooksimport(cardinalitycheck_after_add_entity,cardinalitycheck_before_del_relation,cstrcheck_after_add_relation,uniquecstrcheck_before_modification)self.warning('deactivating verification hooks')self.verification_hooks_activated=Falseself.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', '')defreactivate_verification_hooks(self):fromcubicweb.server.hooksimport(cardinalitycheck_after_add_entity,cardinalitycheck_before_del_relation,cstrcheck_after_add_relation,uniquecstrcheck_before_modification)self.warning('reactivating verification hooks')self.verification_hooks_activated=Trueself.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', '')fromcubicweb.selectorsimportyesfromcubicweb.appobjectimportAppObjectclassautoid(type):"""metaclass to create an unique 'id' attribute on the class using it"""# XXX is this metaclass really necessary ?def__new__(mcs,name,bases,classdict):cls=super(autoid,mcs).__new__(mcs,name,bases,classdict)cls.id=str(id(cls))returnclsclassHook(AppObject):__metaclass__=autoid__registry__='hooks'__select__=yes()# set this in derivated classesevents=Noneaccepts=Noneenabled=Truedef__init__(self,event=None):super(Hook,self).__init__()self.event=event@classmethoddefregistered(cls,vreg):super(Hook,cls).registered(vreg)returncls()@classmethoddefregister_to(cls):ifnotcls.enabled:cls.warning('%s hook has been disabled',cls)returndone=set()assertisinstance(cls.events,(tuple,list)), \'%s: events is expected to be a tuple, not %s'%(cls,type(cls.events))foreventincls.events:ifeventinSYSTEM_HOOKS:assertnotcls.acceptsorcls.accepts==('Any',), \'%s doesnt make sense on %s'%(cls.accepts,event)cls.accepts=('Any',)forertypeincls.accepts:if(event,ertype)indone:continueyieldevent,ertypedone.add((event,ertype))try:eschema=cls.schema.eschema(ertype)exceptKeyError:# relation schemapasselse:foreetypeineschema.specialized_by():if(event,eetype)indone:continueyieldevent,str(eetype)done.add((event,eetype))defmake_callback(self,event):iflen(self.events)==1:returnself.callreturnself.__class__(event=event).calldefcall(self):raiseNotImplementedErrorclassSystemHook(Hook):accepts=()fromloggingimportgetLoggerfromcubicwebimportset_log_methodsset_log_methods(HooksManager,getLogger('cubicweb.hooksmanager'))set_log_methods(Hook,getLogger('cubicweb.hooks'))# base classes for relation propagation ########################################fromcubicweb.server.poolimportPreCommitOperationclassPropagateSubjectRelationHook(Hook):"""propagate permissions and nosy list when new entity are added"""events=('after_add_relation',)# to set in concrete classrtype=Nonesubject_relations=Noneobject_relations=Noneaccepts=None# subject_relations + object_relationsdefcall(self,session,fromeid,rtype,toeid):foreidin(fromeid,toeid):etype=session.describe(eid)[0]ifnotself.schema.eschema(etype).has_subject_relation(self.rtype):returnifrtypeinself.subject_relations:meid,seid=fromeid,toeidelse:assertrtypeinself.object_relationsmeid,seid=toeid,fromeidsession.unsafe_execute('SET E %s P WHERE X %s P, X eid %%(x)s, E eid %%(e)s, NOT E %s P'\%(self.rtype,self.rtype,self.rtype),{'x':meid,'e':seid},('x','e'))classPropagateSubjectRelationAddHook(Hook):"""propagate on existing entities when a permission or nosy list is added"""events=('after_add_relation',)# to set in concrete classrtype=Nonesubject_relations=Noneobject_relations=Noneaccepts=None# (self.rtype,)defcall(self,session,fromeid,rtype,toeid):eschema=self.schema.eschema(session.describe(fromeid)[0])execute=session.unsafe_executeforrelinself.subject_relations:ifeschema.has_subject_relation(rel):execute('SET R %s P WHERE X eid %%(x)s, P eid %%(p)s, ''X %s R, NOT R %s P'%(rtype,rel,rtype),{'x':fromeid,'p':toeid},'x')forrelinself.object_relations:ifeschema.has_object_relation(rel):execute('SET R %s P WHERE X eid %%(x)s, P eid %%(p)s, ''R %s X, NOT R %s P'%(rtype,rel,rtype),{'x':fromeid,'p':toeid},'x')classPropagateSubjectRelationDelHook(Hook):"""propagate on existing entities when a permission is deleted"""events=('after_delete_relation',)# to set in concrete classrtype=Nonesubject_relations=Noneobject_relations=Noneaccepts=None# (self.rtype,)defcall(self,session,fromeid,rtype,toeid):eschema=self.schema.eschema(session.describe(fromeid)[0])execute=session.unsafe_executeforrelinself.subject_relations:ifeschema.has_subject_relation(rel):execute('DELETE R %s P WHERE X eid %%(x)s, P eid %%(p)s, ''X %s R'%(rtype,rel),{'x':fromeid,'p':toeid},'x')forrelinself.object_relations:ifeschema.has_object_relation(rel):execute('DELETE R %s P WHERE X eid %%(x)s, P eid %%(p)s, ''R %s X'%(rtype,rel),{'x':fromeid,'p':toeid},'x')