[Zope3-checkins] CVS: Zope3/lib/python/Zope/App/OFS/Services/ObjectHub - HubEvent.py:1.1 IHubEvent.py:1.1 IObjectHub.py:1.1 ObjectHub.py:1.1 __init__.py:1.1 collaborations.txt:1.1 configure.zcml:1.1 objecthub.gif:1.1

Gary Poster gary@modernsongs.com
Tue, 29 Oct 2002 22:47:49 -0500


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

Added Files:
	HubEvent.py IHubEvent.py IObjectHub.py ObjectHub.py 
	__init__.py collaborations.txt configure.zcml objecthub.gif 
Log Message:
This checkin cleans up the ObjectHub system.

First of all, the Zope.ObjectHub and Zope.App.OFS.Services.LocalObjectHub packages are gone, replaced by Zope.App.OFS.Services.ObjectHub, as per discussion with Jim and Steve.

Second, the hub events have been modified to match Jim's approach with the ObjectEvents (i.e., events are usually handed an object at the get go whenever possible).  Incidentally, this also coincides with making the "moved" hub event implementation actually coincide with the interface (by including fromLocation).  This is as per discussion with Jim.

Third, lookupLocation and lookupHubid have been switched to getLocation and getHubid.  This is a bit of a ninja-checkin or whatever the term is since I didn't bandy this change about beforehand.  :-(  Sorry.  If folks say nay then I will take responsibility for removing this change.

I think that's about it.




=== Added File Zope3/lib/python/Zope/App/OFS/Services/ObjectHub/HubEvent.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: HubEvent.py,v 1.1 2002/10/30 03:47:47 poster Exp $
"""

__metaclass__ = type

from IHubEvent import IObjectRegisteredHubEvent
from IHubEvent import IObjectUnregisteredHubEvent
from IHubEvent import IObjectModifiedHubEvent
from IHubEvent import IObjectMovedHubEvent
from IHubEvent import IObjectRemovedHubEvent
from Zope.App.Traversing import traverse
from Zope.App.Traversing.ITraverser import ITraverser
from Zope.ComponentArchitecture import getAdapter

class HubEvent:
    """Convenient mix-in for HubEvents"""

    hub = None
    hubid = None
    # object = None
    # location = None
    
    def __init__(self, hub, hubid, location=None, object=None):
        # we keep all four, to avoid unnecessary lookups
        # and to give the objecthub an opportunity to do
        # caching of objects
        self.hub = hub
        self.hubid = hubid
        self.__object = object
        self.__location = location
        
    def __getObject(self):
        obj = self.__object
        if obj is None:
            obj = self.__object = self.hub.getObject(self.hubid)
        return obj

    object = property(__getObject)
    
    def __getLocation(self):
        loc = self.__location
        if loc is None:
            loc = self.__location = self.hub.getLocation(self.hubid)
        return loc
    
    location = property(__getLocation)


class ObjectRegisteredHubEvent(HubEvent):
    """A hubid has been freshly created and mapped against an object."""

    __implements__ = IObjectRegisteredHubEvent


class ObjectUnregisteredHubEvent:
    """We are no longer interested in this object.
    
    """    

    hub = None
    hubid = None
    # object = None
    location = None
    
    def __init__(self, hub, hubid, location, object=None):
        # location *must* be supplied because the object hub cannot be
        # relied upon to translate an unregistered hubid
        self.hub = hub
        self.hubid = hubid
        self.__object = object
        self.location = location

    __implements__ = IObjectUnregisteredHubEvent
        
    def __getObject(self):
        obj = self.__object
        if obj is None:
            adapter = getAdapter(self.hub, ITraverser)
            obj = self.__object = adapter.traverse(self.location)
        return obj

    object = property(__getObject)
    
    
class ObjectModifiedHubEvent(HubEvent):
    """An object with a hubid has been modified."""
    
    __implements__ = IObjectModifiedHubEvent
    
    
class ObjectMovedHubEvent(HubEvent):
    """An object with a hubid has had its context changed. Typically, this
       means that it has been moved."""
    
    def __init__(self, hub, hubid, fromLocation, location=None, object=None):
        self.fromLocation = fromLocation
        HubEvent.__init__(self, hub, hubid, location, object)
       
    __implements__ = IObjectMovedHubEvent


class ObjectRemovedHubEvent(ObjectUnregisteredHubEvent):
    """An object with a hubid has been removed."""

    __implements__ = IObjectRemovedHubEvent
    # ...which is a subclass of IObjectUnregisteredHubEvent

    hub = None
    hubid = None
    object = None
    location = None
    
    def __init__(self, hub, hubid, location, object):
        # all four *must* be supplied because the object hub cannot be
        # relied upon to translate an unregistered hubid
        self.hub = hub
        self.hubid = hubid
        self.object = object
        self.location = location


=== Added File Zope3/lib/python/Zope/App/OFS/Services/ObjectHub/IHubEvent.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: IHubEvent.py,v 1.1 2002/10/30 03:47:47 poster Exp $
"""
from Zope.Event.IEvent import IEvent
from Interface.Attribute import Attribute

class IHubEvent(IEvent):
    """Internal Object Hub Event : something has happened to an object for
       which there is a hub id.
       A hub id is a way of refering to an object independent of location.
    """
    hub = Attribute(
        """the originating object hub (and thus the hub for which this
        hubid is pertinent)""")

    object = Attribute("The subject of the event.")

    hubid = Attribute("the object's hub-unique id")

    location = Attribute("An optional object location.")
   

class IRegistrationHubEvent(IHubEvent):
    """The hub registration status of an object has changed
    """


class IObjectRegisteredHubEvent(IRegistrationHubEvent):
    """A hub id has been freshly created and mapped against an object."""


class IObjectUnregisteredHubEvent(IRegistrationHubEvent):
    """We are no longer interested in this object."""
    
    
class IObjectModifiedHubEvent(IHubEvent):
    """An object with a hub id has been modified."""
    
    
class IObjectMovedHubEvent(IHubEvent):
    """An object with a hub id has had its context changed. Typically, this
       means that it has been moved."""

    fromLocation = Attribute("The old location for the object.")


class IObjectRemovedHubEvent(IObjectUnregisteredHubEvent):
    """An object with a hub id has been removed and unregistered."""


=== Added File Zope3/lib/python/Zope/App/OFS/Services/ObjectHub/IObjectHub.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: IObjectHub.py,v 1.1 2002/10/30 03:47:47 poster Exp $
"""

from Zope.Event.IEventChannel import IEventChannel

class ObjectHubError(Exception):
    pass

class IObjectHub(IEventChannel):
    """ObjectHub.
    
    Receives Object Modify Events from the Event Service, and
    changes these into Hub Id Object Modify Events, then passes
    these on to its subscribers.
       
    To map Object Modify Events onto Hub Id Object Modify Events, take
    the location from the Object Modify Event, look up the Hub Id for this
    location, and create an equivalent Hub Id Object Modify Event using this
    Hub Id.
       
    Note that we are concerned with locations and not with Objects.
    An object may have more than one location. That doesn't concern
    us here.
    
    We're only interested in what happens during the time during which
    an object is registered with the hub -- between ObjectRegistered
    and ObjectUnregistered events.  As one consequence of that, we do
    care about object removals, but not (directly) about object
    additions.
       
    Table of decisions about maintaining the location<->Hub Id lookup:
       
      Register
      
         if location already in lookup:
             raise ObjectHubError, as this is implies bad state somewhere
         generate new hub id
         place hub id<->location into lookup, to say that we have an
             interesting object
             
         send out hub id object register event to subscribers
         
      Unregister
         
         if location not in lookup:
             raise ObjectHubError, as this is implies bad state somewhere
         remove location<->hub id from lookup
            
         send out hub id unregister event to subscribers
         
      Modify
         
         if location not in lookup:
             ignore this event, as we're not interested in this object
         else:
             look up hub id for the location
             send out hub id object modify event to subscribers
                
      Move 

         if old_location not in lookup:
             ignore this event, as we're not interested in this object
         elif new_location is in lookup:
             raise ObjectHubError
         else:
             look up hub id for old_location
             change lookup:
                 remove hub id<->old_location
                 add hub id<->new_location
             send out hub id object context-change event to subscribers

      Remove (specializes Unregister)

         if old_location not in lookup:
             ignore this event, as we're not interested in this object
         else:
             look up hub id for old_location
             change lookup:
                 remove hub id<->old_location
             send out hub id object remove event to subscribers
     """
    
    def getHubId(obj_or_loc):
        """Returns the hub id int that is mapped to the given location
        or wrapped object.
        
        Location is either a string, or a sequence of strings.
        It must be absolute, so if it is a string it must start with a '/',
        and if it is a sequence, it must start with an empty string.
        
        ('','whatever','whatever2')
        '/whatever/whatever2'
        
        If there is no hub id, raise Zope.Exceptions.NotFoundError.
        
        """
        
    def getLocation(hubid):
        """Returns a location as a string.
        
        If there is no location, raise Zope.Exceptions.NotFoundError.
        """
        
    def getObject(hubid):
        """Returns an object for the given hub id.
        
        If there is no such hub id, raise Zope.Exceptions.NotFoundError.
        If there is no such object, passes through whatever error
        the traversal service raises.
        """

    def register(obj_or_loc):
        """Returns a new hub id for the given location or wrapped object
        if it is not already registered. 

        It also emits a HubIdObjectRegisteredEvent.  Raises an 
        ObjectHubError if the location was previously registered. 
        """

    def unregister(obj_or_loc_or_hubid):
        """Unregister an object by wrapped object, by location, or by
        hubid.

        It also emits a HubIdObjectUnregisteredEvent. 
        If the hub id or location wasn't registered a 
        Zope.Exceptions.NotFoundError is raised.
        """ 


=== Added File Zope3/lib/python/Zope/App/OFS/Services/ObjectHub/ObjectHub.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: ObjectHub.py,v 1.1 2002/10/30 03:47:47 poster Exp $
"""

from Zope.App.OFS.Services.LocalEventService.LocalServiceSubscribable \
     import LocalServiceSubscribable
from Zope.App.OFS.Services.LocalEventService.ProtoServiceEventChannel \
     import ProtoServiceEventChannel

from IObjectHub import IObjectHub, ObjectHubError
from HubEvent import ObjectRegisteredHubEvent
from HubEvent import ObjectUnregisteredHubEvent
from HubEvent import ObjectModifiedHubEvent
from HubEvent import ObjectMovedHubEvent
from HubEvent import ObjectRemovedHubEvent
from IHubEvent import IHubEvent

from Zope.Exceptions import NotFoundError

from Zope.Event.IObjectEvent import IObjectRemovedEvent, IObjectEvent
from Zope.Event.IObjectEvent import IObjectMovedEvent, IObjectAddedEvent
from Zope.Event.IObjectEvent import IObjectModifiedEvent

from Persistence.BTrees.IOBTree import IOBTree
from Persistence.BTrees.OIBTree import OIBTree
from Zope.ContextWrapper import ContextMethod
from Zope.Proxy.ContextWrapper import isWrapper
from Zope.App.Traversing.ITraverser import ITraverser
from Zope.App.Traversing import getPhysicalPathString
from Zope.App.Traversing import locationAsUnicode
from Zope.Proxy.ProxyIntrospection import removeAllProxies
from Zope.Proxy.ContextWrapper import ContextWrapper
from Zope.ComponentArchitecture import getAdapter

import random
def randid():
    # Return a random number between -2*10**9 and 2*10**9, but not 0.
    abs = random.randrange(1, 2000000001)
    if random.random() < 0.5:
        return -abs
    else:
        return abs

class ObjectHub(ProtoServiceEventChannel):
    
    # this implementation makes the decision to not interact with any
    # object hubs above it: it is a world unto itself, as far as it is 
    # concerned, and if it doesn't know how to do something, it won't
    # ask anything else to try.  Everything else is YAGNI for now.
    
    __implements__ = (
        IObjectHub,
        ProtoServiceEventChannel.__implements__)

    def __init__(self):
        ProtoServiceEventChannel.__init__(self)
        self.__hubid_to_location = IOBTree()
        self.__location_to_hubid = OIBTree()
    
    # XXX this is copied because of some context method problems
    # with moving LocalEventChannel.notify to this _notify via a simple
    # assignment, i.e. _notify = LocalEventChannel.notify
    def _notify(clean_self, wrapped_self, event):
        
        subscriptionses = clean_self.subscriptionsForEvent(event)
        # that's a non-interface shortcut for
        # 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)

    def notify(wrapped_self, event):
        '''See interface ISubscriber'''
        clean_self = removeAllProxies(wrapped_self)
        clean_self._notify(wrapped_self, event)
        if IObjectEvent.isImplementedBy(event):
            # generate NotificationHubEvents only if object is known
            # ie registered
            if IObjectMovedEvent.isImplementedBy(event):
                canonical_location = locationAsUnicode(event.fromLocation)
                hubid = clean_self._lookupHubId(canonical_location)
                if hubid is not None:
                    canonical_new_location = locationAsUnicode(
                        event.location)
                    location_to_hubid = clean_self.__location_to_hubid
                    if location_to_hubid.has_key(canonical_new_location):
                        raise ObjectHubError(
                            'Cannot move to location %s, '
                            'as there is already something there'
                            % canonical_new_location)
                    hubid = location_to_hubid[canonical_location]
                    del location_to_hubid[canonical_location]
                    location_to_hubid[canonical_new_location] = hubid
                    clean_self.__hubid_to_location[hubid] = (
                        canonical_new_location)
                    # send out IObjectMovedHubEvent to plugins
                    event = ObjectMovedHubEvent(
                        wrapped_self, 
                        hubid,
                        canonical_location,
                        canonical_new_location,
                        event.object)
                    clean_self._notify(wrapped_self, event)
            
            else: 
                
                canonical_location = locationAsUnicode(event.location)
                hubid = clean_self._lookupHubId(canonical_location)
                if hubid is not None:
                    
                    if IObjectModifiedEvent.isImplementedBy(event):
                        # send out IObjectModifiedHubEvent to plugins
                        event = ObjectModifiedHubEvent(
                            wrapped_self, 
                            hubid,
                            canonical_location,
                            event.object)
                        clean_self._notify(wrapped_self, event)

                    elif IObjectRemovedEvent.isImplementedBy(event):
                        del clean_self.__hubid_to_location[hubid]
                        del clean_self.__location_to_hubid[canonical_location]
                        # send out IObjectRemovedHubEvent to plugins
                        event = ObjectRemovedHubEvent(
                            event.object,
                            hubid,
                            canonical_location,
                            event.object)
                        clean_self._notify(wrapped_self, event)
    
    notify = ContextMethod(notify)

    def getHubId(self, location):
        '''See interface ILocalObjectHub'''
        if isWrapper(location):
            location = getPhysicalPathString(location)
        hubid = self._lookupHubId(location)
        if hubid is None:
            raise NotFoundError(locationAsUnicode(location))
        else:
            return hubid
    
    def getLocation(self, hubid):
        '''See interface IObjectHub'''
        try:
            return self.__hubid_to_location[hubid]
        except KeyError:
            raise NotFoundError(hubid)
    
    def getObject(self, hubid):
        '''See interface IObjectHub'''
        location = self.getLocation(hubid)
        adapter = getAdapter(self, ITraverser)
        return adapter.traverse(location)
    getObject = ContextMethod(getObject)
    
    def register(wrapped_self, location):
        '''See interface ILocalObjectHub'''
        clean_self = removeAllProxies(wrapped_self)
        if isWrapper(location):
            obj = location
            location = getPhysicalPathString(location)
        else:
            obj = None
        canonical_location=locationAsUnicode(location)
        if not location.startswith(u'/'):
            raise ValueError("Location must be absolute")
        location_to_hubid = clean_self.__location_to_hubid
        if location_to_hubid.has_key(canonical_location):
            raise ObjectHubError(
                'location %s already in object hub' % 
                canonical_location)
        hubid = clean_self._generateHubId(canonical_location)
        location_to_hubid[canonical_location] = hubid

        # send out IObjectRegisteredHubEvent to plugins
        event = ObjectRegisteredHubEvent(
            wrapped_self, 
            hubid,
            canonical_location,
            obj)
        clean_self._notify(wrapped_self, event)
        return hubid
    
    register = ContextMethod(register)
    
    def unregister(wrapped_self, location):
        '''See interface ILocalObjectHub'''
        clean_self = removeAllProxies(wrapped_self)
        if isWrapper(location):
            location = getPhysicalPathString(location)
        elif isinstance(location, int):
            canonical_location=clean_self.getLocation(location)
        else:
            canonical_location=locationAsUnicode(location)
        location_to_hubid = clean_self.__location_to_hubid
        hubid_to_location = clean_self.__hubid_to_location
        try:
            hubid = location_to_hubid[canonical_location]
        except KeyError:
            raise NotFoundError, 'location %s is not in object hub' % \
                canonical_location
        else:
            del hubid_to_location[hubid]
            del location_to_hubid[canonical_location]
            
            # send out IObjectUnregisteredHubEvent to plugins
            event = ObjectUnregisteredHubEvent(
                wrapped_self, 
                hubid,
                canonical_location)
            clean_self._notify(wrapped_self, event)
    
    unregister = ContextMethod(unregister)
    
    ############################################################

    def _generateHubId(self, location):
        index=getattr(self, '_v_nextid', 0)
        if index%4000 == 0: index = randid()
        hubid_to_location=self.__hubid_to_location
        while not hubid_to_location.insert(index, location):
            index=randid()
        self._v_nextid=index+1
        return index

    def _lookupHubId(self, location):
        canonical_location = locationAsUnicode(location) 
        return self.__location_to_hubid.get(canonical_location, None)


=== Added File Zope3/lib/python/Zope/App/OFS/Services/ObjectHub/__init__.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.
# 
##############################################################################
"""Local Object Hub"""

from Zope.ComponentArchitecture import getService
from Zope.Proxy.ContextWrapper import isWrapper
from Zope.App.Traversing import getPhysicalPathString
from Zope.App.Traversing import locationAsUnicode

def normalizeToHubIds(context, *args):
    """given a context and any number of hub ids, physical paths,
    or wrapped objects, returns a normalized list of each item as hubid
    using the ObjectHub closest to the context.
    """
    obHub = getService(context, "ObjectHub")
    args = list(args)
    for ix in len(args):
        arg = args[ix]
        if isinstance(arg, int):
            pass
        elif isinstance(arg, str):
            args[ix] = obHub.lookupHubId(locationAsUnicode(arg))
        elif isWrapper(arg):
            args[ix] = getPhysicalPathString(arg)
    return args

=== Added File Zope3/lib/python/Zope/App/OFS/Services/ObjectHub/collaborations.txt ===
Sample Object-Hub collaborations


  Participants:

    eventService: IEventService

    hub: IObjectHub

    auto_reg_plugin: ISubscriber
        """An autoregistration plugin

         This implements a policy of automatically registring objects
         when they are added. It also implements a policy of
         automatically removing objects that are moved to (or out of)
         special locations.

         This plugin is subscribed to the hub for IObjectAddedEvents and
         IObjectMovedEvents.

         """
     
    plugin1: ISubscriber
         """Some plugin

         This plugin is subscribed to ObjectHubEvents
         """
     
    queue: ISubscriber
         """An event queue plugin.

         This plugin is subscribed to ObjectHubEvents.
         """
     
    path_index: ISubscriber
         """An index that supports searching for objects by their paths

         This plugin is subscribed to ObjectHubEvents
         """
    
    links: ISubscriber
         """A link tracker

         It will sometimes veto removal hub events if removing an
         object would violate referential integrity.
         """         


    creation_view: 
        "some creation view"

    adding: IAdding

    folder: 
        "a folder containing cotent objects"

    some_admin_view:
         "A view that allows an unregistered object to be registered"

    some_management_view:
        "A view for managing the contents of a container"

    objectRemovedEvent:IObjectRemovedEvent
        "An event computed as: ObjectRemovedEvent(location, object)


  Values:
 
    add_event:IObjectAddedEvent
         "Computed as ObjectAddedEvent(newLocation)"

    newLocation: 
         "The location of newObject"

    newObject: 
         "an object object added in a scenario"

    id:Text
         "The given id for the new object"

    object:
         "An object that exists prior to a scenario"

    objectRegisteredHubEvent:IObjectRegisteredHubEvent
         "Computed as ObjectRegisteredHubEvent(hub, hid, location)

    location:
         "The location of object"

    hid:
         "The hub-generated hub-id of the object.



  Scenario: Object created and added to the hub

    creation_view.action()

       adding.add(newObject)

           folder.setObject(id, newObject) 

           eventService.publishEvent(AddEvent(location))

              hub.notify(addedEvent)

                 auto_reg_plugin.notify(addedEvent)

                    hub.registerAdded(location, object)

                        plugin1.notify(objectAddedHubEvent)

                        queue.notify(objectAddedHubEvent)

                        path_index.notify(objectAddedHubEvent)
              
                        links.notify(objectAddedHubEvent)


  Scenario: Previously created object added to the hub

     some_admin_view.action()

        hub.register(location, object)

           plugin1.notify(objectRegisteredHubEvent)
           
           queue.notify(objectRegisteredHubEvent)
           
           path_index.notify(objectRegisteredHubEvent)
              
           links.notify(objectRegisteredHubEvent)


  Scenario: Moved an object that has been registered
           
     some_management_view.action()

        eventService.publishEvent(objectMovedEvent)

           hub.notify(objectMovedEvent)

              auto_reg_plugin.notify(objectMovedEvent)
              # It might have decided to unregister the object
              # on the basis of the destination

              path_index.notify(objectMovedHubEvent)

  Scenario: A previously registered object is deleted

     some_management_view.delete()
     
        del folder[id]

        eventService.publishEvent(objectRemovedEvent)

           hub.notify(objectRemovedEvent)

              plugin1.notify(objectRemovedHubEvent)
           
              queue.notify(objectRemovedHubEvent)
           
              path_index.notify(objectRemovedHubEvent)
              
              links.notify(objectRemovedHubEvent)


  Scenario: A previously registered object is deleted, but would break
            references.  We assume we have a links plugin that tracks
            links between objects.

     some_management_view.delete()
     
        eventService.publishEvent(objectRemovedEvent)

           hub.notify(objectRemovedEvent)

              plugin1.notify(objectRemovedHubEvent)
           
              queue.notify(objectRemovedHubEvent)
           
              path_index.notify(objectRemovedHubEvent)
              
              links.notify(objectRemovedHubEvent)

                 raise "That would break a link"



=== Added File Zope3/lib/python/Zope/App/OFS/Services/ObjectHub/configure.zcml ===
<zopeConfigure
   xmlns='http://namespaces.zope.org/zope'
   xmlns:browser='http://namespaces.zope.org/browser'
   xmlns:service='http://namespaces.zope.org/service'
>

<serviceType id='ObjectHub' 
             interface='.IObjectHub.' />

  <content class='.ObjectHub.'>

    <factory
        id='ObjectHub'
        permission='Zope.ManageServices' />

    <require
        permission="Zope.View"
        attributes="notify lookupRuid lookupLocation getObject register unregister" />
    <require
        permission="Zope.ManageServices"
        attributes="bound unbound subscribe unsubscribe subscribeOnBind
                    unsubscribedFrom subscribedTo" />
  </content>

  <browser:menuItem menu="add_component" for="Zope.App.OFS.Container.IAdding."
     action="ObjectHub"  title='ObjectHub'
     description='An object hub, for cataloging, unique object ids, and more: use sparingly' />
  
  <browser:icon name="zmi_icon" for=".IObjectHub." 
                file="./objecthub.gif" />

  <include package=".Views" />

</zopeConfigure>


=== Added File Zope3/lib/python/Zope/App/OFS/Services/ObjectHub/objecthub.gif ===
  <Binary-ish file>