[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>