[Zope-Checkins] CVS: Zope3/lib/python/Zope/App/OFS/Services/LocalEventService - IPathSubscriber.py:1.1.2.1 LocalEventChannel.py:1.1.2.1 LocalServiceSubscribable.py:1.1.2.1 LocalSubscribable.py:1.1.2.1 LocalSubscriptionAware.py:1.1.2.1 PathSubscriber.py:1.1.2.1 LocalEventService.py:1.1.2.3 localEventService.zcml:1.1.2.2

Gary Poster garyposter@earthlink.net
Fri, 17 May 2002 13:16:16 -0400


Update of /cvs-repository/Zope3/lib/python/Zope/App/OFS/Services/LocalEventService
In directory cvs.zope.org:/tmp/cvs-serv15153/App/OFS/Services/LocalEventService

Modified Files:
      Tag: Zope-3x-branch
	LocalEventService.py localEventService.zcml 
Added Files:
      Tag: Zope-3x-branch
	IPathSubscriber.py LocalEventChannel.py 
	LocalServiceSubscribable.py LocalSubscribable.py 
	LocalSubscriptionAware.py PathSubscriber.py 
Log Message:
1) Fixed some of my own (wrapping) errors in CA and Traversal
2) added IBindingAware interface for services that need to know when they are bound and unbound to a local service manager
3) in subscribables, made event_types into event_type (YAGNI)
4) in subscribables, added ability to unsubscribe per subscription, not just whole-hog
5) in subscribables, added ability to query subscribable for subscriptions of a given subscriber
6) made ISubscribable extremely verbose, to hopefully describe exactly what's going on (see it for more smaller changes)
7) added ISubscriptionAware interface for objects that need to know when they have been subscribed and unsubscribed
8) built out local event service; once there are some views and once I have some more robust tests this *prototype* should by ready to fly.  Note that it is currently a standard service that can be added to any service manager, and thus the design does attempt to address many of the nested service manager issues.
9) as part of this, created the first indirect subscriber (all placeful
subscriptions will usually need to use indirect subscribers in order to retain their context when events are fired): PathSubscriber
10) removed an endless loop between local service managers and  ZopeSecurityPolicy in which both needed the other to function: ZopeSecurityPolicy now explicitly asks for adapters from the global service manager
11) unintentionally retained some of the "default=ComponentLookupError"-type argument signatures from my sandbox, but only within Container and Folder; I won't worry about undoing it though (unless I am otherwise requested) since it seems these interfaces are due for a dict-like overhaul anyway.

Also, if anyone needs a local event service setup for other tests (like the ObjectHub, for instance) see the LocalEventService/tests/EventSetup.py

more tests on the way for the local event service, and for the changes to the subscribable interface



=== Added File Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/IPathSubscriber.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""

Revision information:
$Id: IPathSubscriber.py,v 1.1.2.1 2002/05/17 17:15:45 poster Exp $
"""
from Interface.Attribute import Attribute
from Zope.Event.ISubscriber import IIndirectSubscriber


class IPathSubscriber(IIndirectSubscriber):
    
    def __init__(wrapped_subscriber):
        """creates new PathSubscriber for the given wrapped_subscriber"""
    
    subscriber_path = Attribute(
        """the slash-delineated absolute url to the subscriber"""
        )

=== Added File Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/LocalEventChannel.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""

Revision information:
$Id: LocalEventChannel.py,v 1.1.2.1 2002/05/17 17:15:45 poster Exp $
"""

from LocalSubscribable import LocalSubscribable
from Zope.Event.IEventChannel import IEventChannel
from Zope.ContextWrapper import ContextMethod
from Zope.Proxy.ProxyIntrospection import removeAllProxies
from Zope.Proxy.ContextWrapper import ContextWrapper

class LocalEventChannel(LocalSubscribable):
    
    __implements__ = IEventChannel
        
    def notify(wrapped_self, event):
        clean_self=removeAllProxies(wrapped_self)
        
        subscriptionses = clean_self._registry.getAllForObject(event)

        for subscriptions in subscriptionses:
            
            for subscriber,filter in subscriptions:
                if filter is not None and not filter(event):
                    continue
                ContextWrapper(subscriber, wrapped_self).notify(event)
    
    notify=ContextMethod(notify)

    

    


=== Added File Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/LocalServiceSubscribable.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""

Revision information:
$Id: LocalServiceSubscribable.py,v 1.1.2.1 2002/05/17 17:15:45 poster Exp $
"""

from Zope.ComponentArchitecture.IToIRegistry import TypeRegistry
from Zope.Exceptions import NotFoundError
from Zope.Event.ISubscriptionAware import ISubscriptionAware
from Zope.Event.IEvent import IEvent
from Zope.ContextWrapper import ContextMethod
from Zope.Proxy.ProxyIntrospection import removeAllProxies
from Zope.Proxy.ContextWrapper import ContextWrapper
from LocalSubscribable import LocalSubscribable
from Persistence import Persistent

class LocalServiceSubscribable(LocalSubscribable, Persistent):
    """a local mix-in for services"""
    
    __serviceName=None # replace me
    
    def unsubscribe(wrapped_self, subscriber, event_type=None, filter=None):
        subscriber=removeAllProxies(subscriber) # might be wrapped, might not
        
        clean_self=removeAllProxies(wrapped_self)
        wrapped_subscriber=ContextWrapper(subscriber, wrapped_self)
        
        for subscriber_index in range(len(clean_self._subscribers)):
            sub=clean_self._subscribers[subscriber_index]
            if sub[0]==subscriber:
                ev_set=sub[1]
                break
        else:
            # raise NotFoundError(subscriber)
            getNextService(wrapped_self, clean_self.__serviceName).unsubscribe(
                subscriber, event_type, filter)
            return
        
        
        do_alert=ISubscriptionAware.isImplementedBy(subscriber)
        
        if event_type:
            ev_type=event_type
            if event_type is IEvent:
                ev_type=None # handle optimization
            if ev_type not in ev_set:
                getNextService(wrapped_self, clean_self.__serviceName).unsubscribe(
                    subscriber, event_type, filter)
            else:
                subscriptions = clean_self._registry.getJustForType(ev_type)
                try:
                    subscriptions.remove((subscriber, filter))
                except ValueError:
                    raise NotFoundError(subscriber, event_type, filter)
                if do_alert:
                    wrapped_subscriber.unsubscribedFrom(self, event_type, filter)
                if len(ev_set)==1:
                    for sub in subscriptions:
                        if sub[0]==subscriber:
                            break
                    else:
                        del clean_self._subscribers[subscriber_index]
        else:
            for ev_type in ev_set:
                subscriptions = clean_self._registry.getJustForType(ev_type)
                subs=subscriptions[:]
                subscriptions[:] = []
                for sub in subs:
                    if sub[0] == subscriber: # deleted (not added back)
                        if do_alert:
                            wrapped_subscriber.unsubscribedFrom(
                                wrapped_self, ev_type or IEvent, sub[1])
                            # IEvent switch is to make optimization transparent
                    else: # kept (added back)
                        subscriptions.append(sub)
            del clean_self._subscribers[subscriber_index]
            getNextService(wrapped_self, clean_self.__serviceName).unsubscribe(
                subscriber, event_type, filter)
        clean_self._registry=clean_self._registry #trigger persistence, if pertinent
    
    unsubscribe=ContextMethod(unsubscribe)
    
    def listSubscriptions(wrapped_self, subscriber, event_type=None):
        subscriber=removeAllProxies(subscriber) # might be wrapped, might not
        
        clean_self=removeAllProxies(wrapped_self)
        result=Subscribable.listSubscriptions(clean_self, subscriber, event_type)
        result.extend(getNextService(
            wrapped_self, clean_self.__serviceName).listSubscriptions(
                subscriber, event_type))
        return result
    
    listSubscriptions=ContextMethod(listSubscriptions)


=== Added File Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/LocalSubscribable.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""

Revision information:
$Id: LocalSubscribable.py,v 1.1.2.1 2002/05/17 17:15:45 poster Exp $
"""

from Zope.ComponentArchitecture.IToIRegistry import TypeRegistry
from Zope.Exceptions import NotFoundError
from Zope.Event.ISubscriptionAware import ISubscriptionAware
from Zope.Event.IEvent import IEvent
from Zope.ContextWrapper import ContextMethod
from Zope.Proxy.ProxyIntrospection import removeAllProxies
from Zope.Proxy.ContextWrapper import ContextWrapper
from Zope.Event.Subscribable import Subscribable
from Persistence import Persistent

class LocalSubscribable(Subscribable, Persistent):
    """a local mix-in"""

    def subscribe(wrapped_self, subscriber, event_type=IEvent, filter=None):
        subscriber=removeAllProxies(subscriber) # might be wrapped, might not
        
        clean_self=removeAllProxies(wrapped_self)
        wrapped_subscriber=ContextWrapper(subscriber, wrapped_self)
        
        if ISubscriptionAware.isImplementedBy(subscriber):
            wrapped_subscriber.subscribedTo(wrapped_self, event_type, filter)
        
        ev_type=event_type
        if ev_type is IEvent: ev_type=None # optimization
        
        subscribers = clean_self._registry.getJustForType(ev_type)
        if subscribers is None:
            subscribers = []
            clean_self._registry.register(ev_type, subscribers)
        subscribers.append((subscriber, filter))

        subs = clean_self._subscribers
        for sub in subs:
            if sub[0]==subscriber:
                sub[1][ev_type]=1
                break
        else:
            subs.append((subscriber,{ev_type:1}))
        
        clean_self._registry=clean_self._registry #trigger persistence, if pertinent
        
    
    subscribe=ContextMethod(subscribe)
    
    def unsubscribe(wrapped_self, subscriber, event_type=None, filter=None):
        subscriber=removeAllProxies(subscriber) # might be wrapped, might not
        
        clean_self=removeAllProxies(wrapped_self)
        wrapped_subscriber=ContextWrapper(subscriber, wrapped_self)
        
        for subscriber_index in range(len(clean_self._subscribers)):
            sub=clean_self._subscribers[subscriber_index]
            if sub[0]==subscriber:
                ev_set=sub[1]
                break
        else:
            raise NotFoundError(subscriber)
        
        
        do_alert=ISubscriptionAware.isImplementedBy(subscriber)
        
        if event_type:
            ev_type=event_type
            if event_type is IEvent:
                ev_type=None # handle optimization
            if ev_type not in ev_set:
                raise NotFoundError(subscriber, event_type, filter)
            subscriptions = clean_self._registry.getJustForType(ev_type)
            try:
                subscriptions.remove((subscriber, filter))
            except ValueError:
                raise NotFoundError(subscriber, event_type, filter)
            if do_alert:
                wrapped_subscriber.unsubscribedFrom(self, event_type, filter)
            if len(ev_set)==1:
                for sub in subscriptions:
                    if sub[0]==subscriber:
                        break
                else:
                    del clean_self._subscribers[subscriber_index]
        else:
            for ev_type in ev_set:
                subscriptions = clean_self._registry.getJustForType(ev_type)
                subs=subscriptions[:]
                subscriptions[:] = []
                for sub in subs:
                    if sub[0] == subscriber: # deleted (not added back)
                        if do_alert:
                            wrapped_subscriber.unsubscribedFrom(
                                wrapped_self, ev_type or IEvent, sub[1])
                            # IEvent switch is to make optimization transparent
                    else: # kept (added back)
                        subscriptions.append(sub)
            del clean_self._subscribers[subscriber_index]
        clean_self._registry=clean_self._registry #trigger persistence, if pertinent
    
    unsubscribe=ContextMethod(unsubscribe)


=== Added File Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/LocalSubscriptionAware.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""

Revision information:
$Id: LocalSubscriptionAware.py,v 1.1.2.1 2002/05/17 17:15:45 poster Exp $
"""
from Zope.Event.ISubscriptionAware import ISubscriptionAware
from Zope.Proxy.ProxyIntrospection import removeAllProxies
from Zope.ComponentArchitecture import getAdapter
from Zope.App.Traversing.ITraverser import ITraverser


class LocalSubscriptionAware:
    # a mix-in
    
    __implements__=ISubscriptionAware
    
    def __init__(self):
        self._subscriptions=()
    
    def subscribedTo(self, subscribable, event_type, filter):
        subscribable_path=getAdapter(
            subscribable, ITraverser).getPhysicalPath()
        if (subscribable_path, event_type, filter) not in self._subscriptions:
            self._subscriptions+=((subscribable_path,event_type, filter),)
    
    def unsubscribedFrom(self, subscribable, event_type, filter):
        subscribable_path=getAdapter(
            subscribable, ITraverser).getPhysicalPath()
        sub=list(self._subscriptions)
        sub.remove((subscribable_path, event_type, filter))
        self._subscriptions=sub
        
        
        

=== Added File Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/PathSubscriber.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""

Revision information:
$Id: PathSubscriber.py,v 1.1.2.1 2002/05/17 17:15:45 poster Exp $
"""

from Zope.ComponentArchitecture import getAdapter
from Zope.App.Traversing.ITraverser import ITraverser
from Zope.Event.ISubscriptionAware import ISubscriptionAware
from Zope.Proxy.ProxyIntrospection import removeAllProxies
from IPathSubscriber import IPathSubscriber
from Interface import Attribute

from Zope.ContextWrapper import ContextMethod


class PathSubscriber:
    
    __implements__=IPathSubscriber, ISubscriptionAware
    
    def __init__(self, wrapped_subscriber):
        self.subscriber_path="/%s" % "/".join(getAdapter(
            wrapped_subscriber, ITraverser).getPhysicalPath())
        # XXX right now the conversion to a string is necessary because
        # the tuple path returned by the Traverser does not include an
        # empty initial space to represent the root
        self.__alert_subscription=ISubscriptionAware.isImplementedBy(
            removeAllProxies(wrapped_subscriber) )
    
    def __eq__(self, other):
        return IPathSubscriber.isImplementedBy(other) and \
               other.subscriber_path==self.subscriber_path
    
    def __getSubscriber(self, wrapped_self):
        return getAdapter(wrapped_self, ITraverser).traverse(self.subscriber_path)
    
    def notify(wrapped_self, event):
        removeAllProxies(wrapped_self).__getSubscriber(wrapped_self).notify(event)
    
    notify=ContextMethod(notify)
    
    def subscribedTo(wrapped_self, subscribable, event_type, filter):
        clean_self=removeAllProxies(wrapped_self)
        if clean_self.__alert_subscription:
            clean_self.__getSubscriber(wrapped_self).subscribedTo(
                subscribable, event_type, filter )
    
    subscribedTo=ContextMethod(subscribedTo)
    
    def unsubscribedFrom(wrapped_self, subscribable, event_type, filter):
        clean_self=removeAllProxies(wrapped_self)
        if clean_self.__alert_subscription:
            clean_self.__getSubscriber(wrapped_self).unsubscribedFrom(
                subscribable, event_type, filter )
    
    unsubscribedFrom=ContextMethod(unsubscribedFrom)
    

=== Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/LocalEventService.py 1.1.2.2 => 1.1.2.3 ===
 """
 
-from Persistence import Persistent
 from Zope.Event.IEventService import IEventService
-from Zope.Event.GlobalEventService import GlobalEventService
+from Zope.Event.ISubscriptionAware import ISubscriptionAware
+from Zope.Event.ISubscriber import ISubscriber
+from LocalServiceSubscribable import LocalServiceSubscribable
+from Zope.ComponentArchitecture import getNextService, getAdapter
+from Zope.App.OFS.ServiceManager.IBindingAware import IBindingAware
 from Zope.ContextWrapper import ContextMethod
-from Zope.ComponentArchitecture import getNextService
+from Zope.Proxy.ProxyIntrospection import removeAllProxies
+from Zope.Proxy.ContextWrapper import ContextWrapper
+from PathSubscriber import PathSubscriber
+from Zope.App.Traversing.ITraverser import ITraverser
+from LocalSubscriptionAware import LocalSubscriptionAware
+from Interface.Attribute import Attribute
+from Zope.Event.GlobalEventService import eventService
 
-class LocalEventService(GlobalEventService, Persistent):
+class ILocalEventService(IEventService, ISubscriber, IBindingAware, ISubscriptionAware):
+    
+    def isPromotableEvent(event):
+        """a hook.  Returns a true value if, when publishing an
+        event, the event should also be promoted to the
+        next (higher) level of event service, and a false value
+        otherwise.  The default implementation is to always return
+        true."""
+    
+    subscribeOnBind=Attribute(
+        """if true, event service will subscribe
+        to the parent event service on binding, unless the parent
+        service is the global event service""")
+
+class LocalEventService(LocalServiceSubscribable, LocalSubscriptionAware):
         
+    __implements__ = ILocalEventService
+    
+    __serviceName="Events" # used by LocalServiceSubscribable
+    
+    def __init__(self):
+        LocalServiceSubscribable.__init__(self)
+        LocalSubscriptionAware.__init__(self)
+        self.subscribeOnBind=1
+    
+    def isPromotableEvent(self, event):
+        "see ILocalEventService"
+        return 1
+    
     def publishEvent(wrapped_self, event):
+        "see IEventService"
+        clean_self=removeAllProxies(wrapped_self)
+        
+        subscriptionses = clean_self._registry.getAllForObject(event)
 
-        self.notify(event)
-        getNextService(wrapped_self, 'EventService').publishEvent(event)
+        for subscriptions in subscriptionses:
+            
+            for subscriber,filter in subscriptions:
+                if filter is not None and not filter(event):
+                    continue
+                ContextWrapper(subscriber, wrapped_self).notify(event)
+        publishedEvents=getattr(clean_self, "_v_publishedEvents", None)
+        if publishedEvents is None:
+            publishedEvents=clean_self._v_publishedEvents=[event]
+        else: publishedEvents.append(event)
+        if(clean_self.isPromotableEvent(event)):
+            getNextService(wrapped_self, 'Events').publishEvent(event)
+        publishedEvents.remove(event)
     
     publishEvent=ContextMethod(publishEvent)
+        
+    def notify(wrapped_self, event):
+        "see ISubscriber"
+        clean_self=removeAllProxies(wrapped_self)
+        publishedEvents=getattr(clean_self, "_v_publishedEvents", None)
+        if publishedEvents is None or event not in publishedEvents:
+        
+            subscriptionses = clean_self._registry.getAllForObject(event)
+
+            for subscriptions in subscriptionses:
+                
+                for subscriber,filter in subscriptions:
+                    if filter is not None and not filter(event):
+                        continue
+                    ContextWrapper(subscriber, wrapped_self).notify(event)
+    
+    notify=ContextMethod(notify)
     
     
+    def bound(wrapped_self, name):
+        "see IBindingAware"
+        clean_self=removeAllProxies(wrapped_self)
+        if clean_self.subscribeOnBind:
+            es=getNextService(wrapped_self, name)
+            if es is not eventService: # if we really want the top-level
+                # placeful event service to receive events from the
+                # global event service we're going to have to
+                # set something special up--something that subscribes
+                # every startup...
+                es.subscribe(PathSubscriber(wrapped_self))
+    
+    bound=ContextMethod(bound)
+    
+    def unbound(wrapped_self, name):
+        "see IBindingAware"
+        clean_self=removeAllProxies(wrapped_self)
+        subscriber=PathSubscriber(wrapped_self)
+        clean_self._v_unbinding=1
+        for subscribable_path in clean_self._subscriptions:
+            subscribable=getAdapter(wrapped_self, ITraverser).traverse(subscribable_path)
+            subscribable.unsubscribe(subscriber)
+        clean_self._subscriptions=()
+        for subscriber in clean_self._subscribers:
+            wrapped_self.unsubscribe(subscriber)
+        clean_self._v_unbinding=None
 
+    unbound=ContextMethod(unbound)
     
+    def unsubscribedFrom(wrapped_self, subscribable, event_type, filter):
+        "see ISubscriptionAware"
+        clean_self=removeAllProxies(wrapped_self)
+        if getattr(clean_self, "_v_unbinding", None) is None:
+            # we presumably have been unsubscribed from a higher-level
+            # event service because that event service is unbinding
+            # itself: we need to remove the higher level event service
+            # from our subscriptions list and try to find another event
+            # service to which to attach
+            LocalSubscriptionAware.unsubscribedFrom(clean_self, subscribable, event_type, filter)
+            clean_subscribable=removeAllProxies(subscribable)
+            if IEventService.isImplementedBy(removeAllProxies(clean_subscribable)):
+                context=getService(wrapped_self, "Events")
+                while removeAllProxies(context) is clean_subscribable:
+                    context=getService(context, "Events")
+                # XXX as usual, we *must not* be working with a global service;
+                # this probably should raise an error if service is global service...
+                # that leaves replacing top level event services an
+                # interesting question, however
+                context.subscribe(PathSubscriber(wrapped_self))
+    
+    unsubscribedFrom=ContextMethod(unsubscribedFrom)
+            
+        


=== Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/localEventService.zcml 1.1.2.1 => 1.1.2.2 ===
 >
 
-<service:factoryFromClass name='EventService'
+<service:factoryFromClass name='Events'
              class='.LocalEventService+'
              permission_id='Zope.ManageServices'
-             title='Event Service'
+             title='Events'
              description='An event service: use sparingly' />
 
 </zopeConfigure>