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

Jim Fulton jim@zope.com
Mon, 10 Jun 2002 19:28:41 -0400


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

Added Files:
	IPathSubscriber.py LocalEventChannel.py LocalEventService.py 
	LocalServiceSubscribable.py LocalSubscribable.py 
	LocalSubscriptionAware.py PathSubscriber.py __init__.py 
	localEventService.zcml 
Log Message:
Merged Zope-3x-branch into newly forked Zope3 CVS Tree.


=== Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/IPathSubscriber.py 1.1 => 1.2 ===
+#
+# 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$
+"""
+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"""
+        )
\ No newline at end of file


=== Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/LocalEventChannel.py 1.1 => 1.2 ===
+#
+# 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$
+"""
+
+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)
+
+    
+
+    


=== Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/LocalEventService.py 1.1 => 1.2 ===
+#
+# 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$
+"""
+
+from Zope.Event.IEventService import IEventService
+from Zope.Event.ISubscriptionAware import ISubscriptionAware
+from Zope.Event.ISubscriber import ISubscriber
+from LocalServiceSubscribable import LocalServiceSubscribable
+from Zope.ComponentArchitecture import getNextService, getAdapter, getService
+from Zope.App.OFS.Services.ServiceManager.IBindingAware import IBindingAware
+from Zope.ContextWrapper import ContextMethod
+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
+from Zope.Event.IEvent import IEvent
+
+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
+    
+    subscribeOnBind=1
+    
+    def __init__(self):
+        LocalServiceSubscribable.__init__(self)
+        LocalSubscriptionAware.__init__(self)
+    
+    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)
+
+        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 subscription in clean_self._subscriptions:
+            subscribable=getAdapter(
+                wrapped_self, ITraverser).traverse(subscription[0])
+            subscribable.unsubscribe(subscriber)
+        clean_self._subscriptions=()
+        for subscriber in clean_self._subscribers:
+            clean_self.__unsubscribeAllFromSelf(wrapped_self, subscriber[0])
+        clean_self._v_unbinding=None
+
+    unbound=ContextMethod(unbound)
+    
+    def __unsubscribeAllFromSelf(clean_self, wrapped_self, subscriber):
+        """this is *not* an interface function, and should not be used
+        outside of this class"""
+    
+        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)
+        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
+    
+    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")
+                # we do this instead of getNextService because the order
+                # of unbinding and notification of unbinding is not
+                # guaranteed
+                while removeAllProxies(context) in (
+                    clean_subscribable, clean_self): 
+                    context=getNextService(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/LocalServiceSubscribable.py 1.1 => 1.2 ===
+#
+# 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$
+"""
+
+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
+from Zope.ComponentArchitecture import getNextService
+
+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
+    
+    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=LocalSubscribable.listSubscriptions(
+            clean_self, subscriber, event_type)
+        result.extend(getNextService(
+            wrapped_self, clean_self._serviceName).listSubscriptions(
+                subscriber, event_type))
+        return result
+    
+    listSubscriptions=ContextMethod(listSubscriptions)


=== Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/LocalSubscribable.py 1.1 => 1.2 ===
+#
+# 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$
+"""
+
+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
+        
+    
+    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)
+            if not subscriptions:
+                raise NotFoundError(subscriber, event_type, filter)
+            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
+    
+    unsubscribe=ContextMethod(unsubscribe)


=== Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/LocalSubscriptionAware.py 1.1 => 1.2 ===
+#
+# 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$
+"""
+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()
+        subscribable_path="/%s" % "/".join(getAdapter(
+            subscribable, 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
+        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="/%s" % "/".join(getAdapter(
+            subscribable, ITraverser).getPhysicalPath())
+        sub=list(self._subscriptions)
+        sub.remove((subscribable_path, event_type, filter))
+        self._subscriptions=tuple(sub)
+        
+        
+        
\ No newline at end of file


=== Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/PathSubscriber.py 1.1 => 1.2 ===
+#
+# 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$
+"""
+
+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)
+    
\ No newline at end of file


=== Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/__init__.py 1.1 => 1.2 ===
+#
+# 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.
+# 
+##############################################################################
+"""Local Event Service"""
+
+


=== Zope3/lib/python/Zope/App/OFS/Services/LocalEventService/localEventService.zcml 1.1 => 1.2 ===
+   xmlns='http://namespaces.zope.org/zope'
+   xmlns:security='http://namespaces.zope.org/security'
+   xmlns:zmi='http://namespaces.zope.org/zmi'
+   xmlns:browser='http://namespaces.zope.org/browser'
+   xmlns:service='http://namespaces.zope.org/service'
+>
+
+  <content class='.LocalEventService.'>
+    <security:require
+        permission="Zope.View"
+        attributes="publishEvent notify" />
+    <security:require
+        permission="Zope.ManageServices"
+        attributes="bound unbound subscribe unsubscribe subscribeOnBind
+                    unsubscribedFrom subscribedTo" />
+  </content>
+  
+  <service:factoryFromClass
+      id='Events'
+      class='.LocalEventService.'
+      permission='Zope.ManageServices'
+      title='Events'
+      description='An event service: use sparingly' />
+
+  <include package=".Views" 
+           file="views.zcml" />
+
+</zopeConfigure>