Back to index

plone3  3.1.7
handlers.py
Go to the documentation of this file.
00001 from zope.component import queryUtility
00002 from zope.component.interfaces import IObjectEvent
00003 
00004 import zope.thread
00005 
00006 from plone.contentrules.engine.interfaces import IRuleStorage
00007 from plone.contentrules.engine.interfaces import IRuleExecutor
00008 from plone.contentrules.engine.interfaces import StopRule
00009 
00010 from Acquisition import aq_inner, aq_parent
00011 from Products.CMFCore.interfaces import ISiteRoot
00012 from Products.CMFCore.utils import getToolByName
00013 from Products.Archetypes.interfaces import IBaseObject
00014 from Products.Archetypes.interfaces import IObjectInitializedEvent
00015 
00016 class DuplicateRuleFilter(object):
00017     """A filter which can prevent rules from being executed more than once
00018     regardless of context.
00019     """
00020     
00021     def __init__(self):
00022         self.reset()
00023 
00024     def reset(self):
00025         self.executed = set()
00026         self.in_progress = False
00027 
00028     def __call__(self, context, rule, event):
00029         obj = context
00030         if IObjectEvent.providedBy(event):
00031             obj = event.object
00032         
00033         uid_method = getattr(obj, 'UID', None)
00034         if uid_method is not None:
00035             uid = uid_method()
00036         else:
00037             uid = '/'.join(context.getPhysicalPath())
00038         if (uid, rule.__name__,) in self.executed:
00039             return False
00040         else:
00041             self.executed.add((uid, rule.__name__,))
00042             return True
00043 
00044 # A thread local for keeping track of rule execution across events
00045 _status = zope.thread.local()
00046 
00047 def init():
00048     if not hasattr(_status, 'rule_filter'):
00049         _status.rule_filter = DuplicateRuleFilter()
00050     if not hasattr(_status, 'delayed_events'):
00051         _status.delayed_events = {}
00052 
00053 def close(event):
00054     """Close the event processing when the request ends
00055     """
00056     if hasattr(_status, 'rule_filter'):
00057         _status.rule_filter.reset()
00058     if hasattr(_status, 'delayed_events'):
00059         _status.delayed_events = {}
00060     
00061 def execute(context, event):
00062     """Execute all rules relative to the context, and bubble as appropriate.
00063     """
00064     
00065     # Do nothing if there is no rule storage or it is not active
00066     storage = queryUtility(IRuleStorage)
00067     if storage is None or not storage.active:
00068         return
00069     
00070     init()
00071     
00072     rule_filter = _status.rule_filter
00073     
00074     # Stop if someone else is already executing. This could happen if,
00075     # for example, a rule triggered here caused another event to be fired.
00076     if rule_filter.in_progress:
00077         return
00078         
00079     # Tell other event handlers to be equally kind
00080     rule_filter.in_progress = True
00081     
00082     # Prepare to break hard if a rule demanded execution be stopped
00083     try:
00084     
00085         # Try to execute rules in the context. It may not work if the context
00086         # is not a rule executor, but we may still want to bubble events
00087         executor = IRuleExecutor(context, None)
00088         if executor is not None:
00089             executor(event, bubbled=False, rule_filter=rule_filter)
00090     
00091         # Do not bubble beyond the site root
00092         if not ISiteRoot.providedBy(context):
00093             parent = aq_parent(aq_inner(context))
00094             while parent is not None:
00095                 executor = IRuleExecutor(parent, None)
00096                 if executor is not None:
00097                     executor(event, bubbled=True, rule_filter=rule_filter)
00098                 if ISiteRoot.providedBy(parent):
00099                     parent = None
00100                 else:
00101                     parent = aq_parent(aq_inner(parent))
00102 
00103     except StopRule:
00104         pass
00105     
00106     # We are done - other events that occur after this one will be allowed to
00107     # execute rules again
00108     rule_filter.in_progress = False
00109 
00110     
00111 # Event handlers
00112 
00113 def is_portal_factory(context):
00114     """Find out if the given object is in portal_factory
00115     """
00116     portal_factory = getToolByName(context, 'portal_factory', None)
00117     if portal_factory is not None:
00118         return portal_factory.isTemporary(context)
00119     else:
00120         return False
00121 
00122 def added(event):
00123     """When an object is added, execute rules assigned to its new parent.
00124 
00125     There is special handling for Archetypes objects.
00126     """
00127     if is_portal_factory(event.object):
00128         return
00129     
00130     # The object added event executes too early for Archetypes objects.
00131     # We need to delay execution until we receive a subsequent IObjectInitializedEvent
00132     
00133     if not IBaseObject.providedBy(event.object):
00134         execute(event.newParent, event)
00135     else:
00136         init()
00137         _status.delayed_events[IObjectInitializedEvent] = event
00138 
00139 def archetypes_initialized(event):
00140     """Pick up the delayed IObjectAddedEvent when an Archetypes object is
00141     initialised.
00142     """
00143     if is_portal_factory(event.object):
00144         return
00145     
00146     if not IBaseObject.providedBy(event.object):
00147         return
00148 
00149     init()
00150     delayed_event = _status.delayed_events.get(IObjectInitializedEvent, None)
00151     if delayed_event is not None:
00152         _status.delayed_events[IObjectInitializedEvent] = None
00153         execute(delayed_event.newParent, delayed_event)
00154         
00155 def removed(event):
00156     """When an IObjectRemevedEvent was received, execute rules assigned to its
00157      previous parent.
00158     """
00159     
00160     if is_portal_factory(event.object):
00161         return
00162         
00163     execute(event.oldParent, event)
00164     
00165 def modified(event):
00166     """When an object is modified, execute rules assigned to its parent
00167     """
00168     
00169     if is_portal_factory(event.object):
00170         return
00171     
00172     # Let the special handler take care of IObjectInitializedEvent
00173     if not IObjectInitializedEvent.providedBy(event):
00174         execute(aq_parent(aq_inner(event.object)), event)
00175         
00176 def workflow_action(event):
00177     """When a workflow action is invoked on an object, execute rules assigned
00178     to its parent.
00179     """
00180     
00181     if is_portal_factory(event.object):
00182         return
00183     
00184     execute(aq_parent(aq_inner(event.object)), event)