[Zope3-checkins] CVS: Zope3/src/zope/app/services - __init__.py:1.2 adapter.py:1.2 auth.py:1.2 auth.txt:1.2 cache.py:1.2 configuration.py:1.2 configurationmanager.py:1.2 configure.zcml:1.2 connection.py:1.2 errorr.py:1.2 event.py:1.2 field.py:1.2 hub.py:1.2 hubcollaborations.txt:1.2 module.py:1.2 package.py:1.2 principalannotation.py:1.2 role.py:1.2 service.py:1.2 session.py:1.2 session.txt:1.2 test_cookiesessionservice.py:1.2 view.py:1.2 viewpackage.py:1.2 zpt.py:1.2

Jim Fulton jim@zope.com
Wed, 25 Dec 2002 09:13:53 -0500


Update of /cvs-repository/Zope3/src/zope/app/services
In directory cvs.zope.org:/tmp/cvs-serv15352/src/zope/app/services

Added Files:
	__init__.py adapter.py auth.py auth.txt cache.py 
	configuration.py configurationmanager.py configure.zcml 
	connection.py errorr.py event.py field.py hub.py 
	hubcollaborations.txt module.py package.py 
	principalannotation.py role.py service.py session.py 
	session.txt test_cookiesessionservice.py view.py 
	viewpackage.py zpt.py 
Log Message:
Grand renaming:

- Renamed most files (especially python modules) to lower case.

- Moved views and interfaces into separate hierarchies within each
  project, where each top-level directory under the zope package
  is a separate project.

- Moved everything to src from lib/python.

  lib/python will eventually go away. I need access to the cvs
  repository to make this happen, however.

There are probably some bits that are broken. All tests pass
and zope runs, but I haven't tried everything. There are a number
of cleanups I'll work on tomorrow.



=== Zope3/src/zope/app/services/__init__.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:50 2002
+++ Zope3/src/zope/app/services/__init__.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.


=== Zope3/src/zope/app/services/adapter.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:50 2002
+++ Zope3/src/zope/app/services/adapter.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,169 @@
+##############################################################################
+#
+# Copyright (c) 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.
+#
+##############################################################################
+"""Adapter Service
+
+
+$Id$
+"""
+__metaclass__ = type
+
+from zope.interface.adapter import AdapterRegistry
+from persistence import Persistent
+from persistence.dict import PersistentDict
+from zope.component.interfaces import IAdapterService
+from zope.component.exceptions import ComponentLookupError
+from zope.component import getServiceManager
+from zope.app.interfaces.services.configuration import IConfigurable
+from zope.app.services.configuration import ConfigurationRegistry
+from zope.app.services.configuration import SimpleConfiguration
+from zope.proxy.context import ContextWrapper
+from zope.proxy.context import ContextMethod
+from zope.app.services.configuration import ConfigurationStatusProperty
+from zope.app.component.nextservice import getNextService
+
+from zope.app.interfaces.services.interfaces import IAdapterConfiguration
+
+class PersistentAdapterRegistry(Persistent, AdapterRegistry):
+
+    def __init__(self):
+        AdapterRegistry.__init__(self, PersistentDict())
+
+
+class AdapterService(Persistent):
+
+    __implements__ = IAdapterService, IConfigurable
+
+    def __init__(self):
+        self._byName = PersistentDict()
+
+    def queryConfigurationsFor(self, configuration, default=None):
+        "See IConfigurable"
+        # XXX Need to add named adapter support
+        return self.queryConfigurations(
+            configuration.forInterface, configuration.providedInterface, '',
+            default)
+
+    queryConfigurationsFor = ContextMethod(queryConfigurationsFor)
+
+    def queryConfigurations(self,
+                            forInterface, providedInterface, name,
+                            default=None):
+
+        adapters = self._byName.get(name)
+        if adapters is None:
+            return default
+
+        registry = adapters.getRegistered(forInterface, providedInterface)
+        if registry is None:
+            return default
+
+        return ContextWrapper(registry, self)
+
+    queryConfigurations = ContextMethod(queryConfigurations)
+
+    def createConfigurationsFor(self, configuration):
+        "See IConfigurable"
+        # XXX Need to add named adapter support
+        return self.createConfigurations(
+            configuration.forInterface, configuration.providedInterface, '')
+
+    createConfigurationsFor = ContextMethod(createConfigurationsFor)
+
+    def createConfigurations(self, forInterface, providedInterface, name):
+
+        adapters = self._byName.get(name)
+        if adapters is None:
+            adapters = PersistentAdapterRegistry()
+            self._byName[name] = adapters
+
+        registry = adapters.getRegistered(forInterface, providedInterface)
+        if registry is None:
+            registry = ConfigurationRegistry()
+            adapters.register(forInterface, providedInterface, registry)
+
+        return ContextWrapper(registry, self)
+
+    createConfigurations = ContextMethod(createConfigurations)
+
+    def getAdapter(self, object, interface, name=''):
+        "See IAdapterService"
+        adapter = self.queryAdapter(object, interface, None, name)
+        if adapter is None:
+            raise ComponentLookupError(object, interface)
+        return adapter
+
+    getAdapter = ContextMethod(getAdapter)
+
+    def queryAdapter(self, object, interface, default=None, name=''):
+        "See IAdapterService"
+        if not name and interface.isImplementedBy(object):
+            return object
+
+        adapters = self._byName.get(name)
+        if adapters:
+
+            registry = adapters.getForObject(
+                object, interface,
+                filter = lambda registry:
+                         ContextWrapper(registry, self).active(),
+                )
+
+            if registry is not None:
+                registry = ContextWrapper(registry, self)
+                adapter = registry.active().getAdapter(object)
+                return adapter
+
+        adapters = getNextService(self, 'Adapters')
+
+        return adapters.queryAdapter(object, interface, default)
+
+    queryAdapter = ContextMethod(queryAdapter)
+
+    # XXX need to add name support
+    def getRegisteredMatching(self,
+                              for_interfaces=None,
+                              provided_interfaces=None):
+
+        adapters = self._byName.get('')
+        if adapters is None:
+            return ()
+
+        return adapters.getRegisteredMatching(for_interfaces,
+                                              provided_interfaces)
+
+class AdapterConfiguration(SimpleConfiguration):
+
+    __implements__ = IAdapterConfiguration
+
+    status = ConfigurationStatusProperty('Adapters')
+
+    # XXX These should be positional arguments, except that forInterface
+    #     isn't passed in if it is omitted. To fix this, we need a
+    #     required=False,explicitly_unrequired=True in the schema field
+    #     so None will get passed in.
+    def __init__(self, forInterface=None, providedInterface=None,
+                 factoryName=None):
+        if None in (providedInterface, factoryName):
+            raise TypeError(
+                "Must provide 'providedInterface' and 'factoryName'")
+        self.forInterface = forInterface
+        self.providedInterface = providedInterface
+        self.factoryName = factoryName
+
+    def getAdapter(self, object):
+        sm = getServiceManager(self)
+        factory = sm.resolve(self.factoryName)
+        return factory(object)
+
+    getAdapter = ContextMethod(getAdapter)


=== Zope3/src/zope/app/services/auth.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:50 2002
+++ Zope3/src/zope/app/services/auth.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,166 @@
+##############################################################################
+#
+# Copyright (c) 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.
+#
+##############################################################################
+"""
+$Id$
+"""
+from types import TupleType
+
+from zope.exceptions import NotFoundError
+from zope.component import getAdapter, queryAdapter
+
+from zope.app.interfaces.container import IContainer
+from zope.app.container.btree import BTreeContainer
+
+from zope.app.interfaces.security import ILoginPassword
+from zope.app.interfaces.security import IAuthenticationService
+
+from zope.app.interfaces.services.auth import IUser
+
+class DuplicateLogin(Exception): pass
+class DuplicateId(Exception): pass
+
+class ILocalAuthenticationService(IAuthenticationService, IContainer):
+    """TTW manageable authentication service"""
+
+    def getAllUsers():
+        """Get all users of the Service."""
+
+
+class AuthenticationService(BTreeContainer):
+
+    __implements__ = ILocalAuthenticationService
+
+    def __init__(self):
+        super(AuthenticationService, self).__init__()
+
+    def getPrincipalByLogin(self, login):
+        for p in self.values():
+            if p.getLogin() == login:
+                return p
+        return None
+
+    def getAllUsers(self):
+        return self.values()
+
+    def authenticate(self, request):
+        'See IAuthenticationService'
+        a = queryAdapter(request, ILoginPassword, None)
+        if a is not None:
+            login = a.getLogin()
+            if login is not None:
+                p = self.getPrincipalByLogin(login)
+                if p is not None:
+                    password = a.getPassword()
+                    if p.validate(password):
+                        return p
+        return None
+
+    def unauthenticatedPrincipal(self):
+        'See IAuthenticationService'
+        return None
+
+    def unauthorized(self, id, request):
+        'See IAuthenticationService'
+        # XXX This is a mess. request has no place here!
+        if id is None:
+            a = getAdapter(request, ILoginPassword)
+            a.needLogin(realm="zope")
+
+    def getPrincipal(self, id):
+        'See IAuthenticationService'
+        r = self.get(id)
+        return r
+
+    def getPrincipals(self, name):
+        'See IAuthenticationService'
+        name = name.lower()
+        return [p for p in self.values()
+                  if p.getTitle().lower().startswith(name) or
+                     p.getLogin().lower().startswith(name)]
+
+
+
+"""A persistent implementation of thr IPrincipal interface
+
+$Id$
+"""
+from persistence import Persistent
+from zope.proxy.introspection import removeAllProxies
+from zope.app.interfaces.annotation import IAttributeAnnotatable
+from zope.app.attributeannotations import AttributeAnnotations
+from zope.app.interfaces.services.auth import IUser
+from zope.app.security.grants.principalrolemanager import \
+     principalRoleManager
+
+class User(Persistent):
+    """A persistent implementation of the IUser interface """
+
+    __implements__ =  IUser, IAttributeAnnotatable
+
+    def __init__(self, id, title, description, login, pw):
+        self.__id = id
+        self.__title = title
+        self.__description = description
+        self.__login = login
+        self.__pw = pw
+
+    def getLogin(self):
+        'See IReadUser'
+        return self.__login
+
+    def getRoles(self):
+        'See IReadUser'
+        annotations = AttributeAnnotations(self)
+        roles = annotations.get('roles', [])
+        roles = removeAllProxies(roles)
+        return roles
+
+    def validate(self, pw):
+        'See IReadUser'
+        return pw == self.__pw
+
+    def getId(self):
+        'See IPrincipal'
+        return self.__id
+
+    def getTitle(self):
+        'See IPrincipal'
+        return self.__title
+
+    def getDescription(self):
+        'See IPrincipal'
+        return self.__description
+
+    def setTitle(self, title):
+        'See IWriteUser'
+        self.__title = title
+
+    def setDescription(self, description):
+        'See IWriteUser'
+        self.__description = description
+
+    def setLogin(self, login):
+        'See IWriteUser'
+
+    def setPassword(self, password):
+        'See IWriteUser'
+        self.__pw = password
+
+    def setRoles(self, roles):
+        'See IReadUser'
+        annotations = AttributeAnnotations(self)
+        annotations['roles'] = roles
+
+    #
+    ############################################################


=== Zope3/src/zope/app/services/auth.txt 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:50 2002
+++ Zope3/src/zope/app/services/auth.txt	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,54 @@
+$Id$
+
+The current implementation will be replaced. Following is design
+I came up with together with Jim Fulton.
+   -- itamar
+
+
+Design notes for new AuthenticationService
+==========================================
+
+The service contains a list of user sources. They implement interfaces,
+starting with:
+
+
+ class IUserPassUserSource:
+     """Authenticate using username and password."""
+     def authenticate(username, password):
+         "Returns boolean saying if such username/password pair exists"
+
+
+ class IDigestSupportingUserSource(IUserPassUserSource):
+     """Allow fetching password, which is required by digest auth methods"""
+     def getPassword(username):
+         "Return password for username"
+
+
+etc.. Probably there will be others as well, for dealing with certificate
+authentication and what not. Probably we need to expand above interfaces
+to deal with principal titles and descriptions, and so on.
+
+A login method - cookie auth, HTTP basic auth, digest auth, FTP auth,
+is registered as a view on one of the above interfaces. 
+
+
+  class ILoginMethodView:
+        def authenticate():
+             """Return principal for request, or None."""
+        def unauthorized():
+             """Tell request that a login is required."""
+
+
+The authentication service is then implemented something like this:
+
+
+ class AuthenticationService:
+     def authenticate(self, request):
+         for us in self.userSources:
+              loginView = getView(self, us, "login", request)
+              principal = loginView.authenticate()
+              if principal is not None:
+                  return principal
+     def unauthorized(self, request):
+         loginView = getView(self, self.userSources[0], request)
+         loginView.unauthorized()


=== Zope3/src/zope/app/services/cache.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:50 2002
+++ Zope3/src/zope/app/services/cache.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,126 @@
+##############################################################################
+#
+# Copyright (c) 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.
+#
+##############################################################################
+"""Caching service.
+
+$Id$
+"""
+__metaclass__ = type
+
+from persistence import Persistent
+from zope.app.interfaces.cache.cache import ICachingService
+from zope.app.component.nextservice import queryNextService
+from zope.app.interfaces.services.configuration \
+        import INameComponentConfigurable
+from zope.app.services.configuration import NameComponentConfigurable
+from zope.app.services.event \
+        import ProtoServiceEventChannel
+from zope.proxy.context import ContextMethod
+from zope.interfaces.event import IEventChannel
+from zope.app.interfaces.event import IObjectModifiedEvent
+
+
+class ILocalCachingService(ICachingService, IEventChannel,
+                           INameComponentConfigurable):
+    """TTW manageable caching service"""
+
+
+class CachingService(ProtoServiceEventChannel, NameComponentConfigurable):
+
+    __implements__ = (ILocalCachingService,
+                      ProtoServiceEventChannel.__implements__)
+
+    _subscribeToServiceInterface = IObjectModifiedEvent
+
+    def __init__(self):
+        # XXX If we know that all the superclasses do the right thing in
+        #     __init__ with respect to calling
+        #     super(ClassName, self).__init__(*args, **kw), then we can
+        #     replace the following with just a call to super.
+        Persistent.__init__(self)
+        ProtoServiceEventChannel.__init__(self)
+        NameComponentConfigurable.__init__(self)
+
+    def getCache(wrapped_self, name):
+        'See ICachingService'
+        cache = wrapped_self.queryActiveComponent(name)
+        if cache:
+            return cache
+        service = queryNextService(wrapped_self, "Caching")
+        if service is not None:
+            return service.getCache(name)
+        raise KeyError, name
+    getCache = ContextMethod(getCache)
+
+    def queryCache(wrapped_self, name, default=None):
+        'See ICachingService'
+        try:
+            return wrapped_self.getCache(name)
+        except KeyError:
+            return default
+    queryCache = ContextMethod(queryCache)
+
+    def getAvailableCaches(wrapped_self):
+        'See ICachingService'
+        caches = {}
+        for name in wrapped_self.listConfigurationNames():
+            registry = wrapped_self.queryConfigurations(name)
+            if registry.active() is not None:
+                caches[name] = 0
+        service = queryNextService(wrapped_self, "Caching")
+        if service is not None:
+            for name in service.getAvailableCaches():
+                caches[name] = 0
+        return caches.keys()
+    getAvailableCaches = ContextMethod(getAvailableCaches)
+
+
+
+"""A configuration for a cache.
+
+$Id$
+"""
+
+from zope.app.interfaces.services.cache import ICacheConfiguration
+from zope.app.services.configuration import NamedComponentConfiguration
+from zope.app.services.configuration import ConfigurationStatusProperty
+from zope.component import getService
+from zope.app.interfaces.event import IObjectModifiedEvent
+from zope.proxy.context import ContextMethod
+
+class CacheConfiguration(NamedComponentConfiguration):
+
+    __doc__ = ICacheConfiguration.__doc__
+
+    __implements__ = (ICacheConfiguration,
+                      NamedComponentConfiguration.__implements__)
+
+    status = ConfigurationStatusProperty('Caching')
+
+    label = "Cache"
+
+    def __init__(self, *args, **kw):
+        super(CacheConfiguration, self).__init__(*args, **kw)
+
+    def activated(wrapped_self):
+        cache = wrapped_self.getComponent()
+        service = getService(wrapped_self, 'Caching')
+        service.subscribe(cache, IObjectModifiedEvent)
+    activated = ContextMethod(activated)
+
+    def deactivated(wrapped_self):
+        cache = wrapped_self.getComponent()
+        service = getService(wrapped_self, 'Caching')
+        service.unsubscribe(cache, IObjectModifiedEvent)
+        cache.invalidateAll()
+    deactivated = ContextMethod(deactivated)


=== Zope3/src/zope/app/services/configuration.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:50 2002
+++ Zope3/src/zope/app/services/configuration.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,412 @@
+##############################################################################
+#
+# Copyright (c) 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.
+#
+##############################################################################
+"""Component registration support for services
+
+$Id$
+"""
+__metaclass__ = type
+
+from persistence import Persistent
+from zope.app.interfaces.services.configuration import IConfigurationRegistry, IConfiguration
+from zope.app.interfaces.services.configuration import INamedConfiguration
+from zope.app.interfaces.services.configuration import INamedComponentConfiguration
+from zope.app.interfaces.services.configuration import INameConfigurable
+from zope.app.interfaces.services.configuration import INameComponentConfigurable
+from zope.component import getService, queryService
+from zope.component import getServiceManager
+from zope.component import getAdapter
+from zope.proxy.context import ContextMethod
+from zope.proxy.context import ContextWrapper
+from zope.proxy.introspection import removeAllProxies
+from zope.security.proxy import Proxy
+from zope.security.checker import InterfaceChecker
+from zope.app.interfaces.container import IAddNotifiable
+from zope.app.interfaces.container import IDeleteNotifiable
+from zope.app.interfaces.dependable import IDependable
+from zope.app.interfaces.dependable import DependencyError
+from zope.app.traversing import getPhysicalPathString, traverse
+from zope.app.traversing import getPhysicalRoot
+from zope.app.interfaces.services.configuration \
+     import Unregistered, Registered, Active
+
+
+class ConfigurationStatusProperty:
+
+    __Zope_ContextWrapper_contextful_get__ = True
+    __Zope_ContextWrapper_contextful_set__ = True
+
+    def __init__(self, service):
+        self.service = service
+
+    def __get__(self, inst, klass):
+        if inst is None:
+            return self
+
+        configuration = inst
+        service = queryService(configuration, self.service)
+        registry = service and service.queryConfigurationsFor(configuration)
+
+        if registry:
+
+            if registry.active() == configuration:
+                return Active
+            if registry.registered(configuration):
+                return Registered
+
+        return Unregistered
+
+    def __set__(self, inst, value):
+        configuration = inst
+        service = queryService(configuration, self.service)
+        registry = service and service.queryConfigurationsFor(configuration)
+
+        if value == Unregistered:
+            if registry:
+                registry.unregister(configuration)
+
+        else:
+            if not service:
+                # raise an error
+                service = getService(configuration, self.service)
+
+            if registry is None:
+                registry = service.createConfigurationsFor(configuration)
+
+            if value == Registered:
+                if registry.active() == configuration:
+                    registry.deactivate(configuration)
+                else:
+                    registry.register(configuration)
+
+            elif value == Active:
+                if not registry.registered(configuration):
+                    registry.register(configuration)
+                registry.activate(configuration)
+
+
+class ConfigurationRegistry(Persistent):
+
+    __implements__ = IConfigurationRegistry
+
+    _data = ()
+
+    def _id(self, ob):
+
+        # Get and check relative path
+        prefix = "/++etc++Services/Packages/"
+        path = getPhysicalPathString(ob)
+        lpackages = path.rfind(prefix)
+        if lpackages < 0:
+            raise ValueError("Configuration object is in an invalid location",
+                             path)
+
+        rpath = path[lpackages+len(prefix):]
+        if not rpath or (".." in rpath.split("/")):
+            raise ValueError("Configuration object is in an invalid location",
+                             path)
+
+        return rpath
+
+    def register(wrapped_self, configuration):
+        cid = wrapped_self._id(configuration)
+
+        if wrapped_self._data:
+            if cid in wrapped_self._data:
+                return # already registered
+        else:
+            # Nothing registered. Need to stick None in front so that nothing
+            # is active.
+            wrapped_self._data = (None, )
+
+        wrapped_self._data += (cid, )
+    register = ContextMethod(register)
+
+    def unregister(wrapped_self, configuration):
+        cid = wrapped_self._id(configuration)
+
+        data = wrapped_self._data
+        if data:
+            if data[0] == cid:
+                # It's active, we need to switch in None
+                data = (None, ) + data[1:]
+
+                # we need to notify it that it's inactive.
+                configuration.deactivated()
+
+            else:
+                data = tuple([item for item in data if item != cid])
+
+        # Check for empty registry
+        if len(data) == 1 and data[0] is None:
+            data = ()
+
+        wrapped_self._data = data
+    unregister = ContextMethod(unregister)
+
+    def registered(wrapped_self, configuration):
+        cid = wrapped_self._id(configuration)
+        return cid in wrapped_self._data
+    registered = ContextMethod(registered)
+
+    def activate(wrapped_self, configuration):
+        cid = wrapped_self._id(configuration)
+        data = wrapped_self._data
+
+        if cid in data:
+
+            if data[0] == cid:
+                return # already active
+
+            if data[0] is None:
+                # Remove leading None marker
+                data = data[1:]
+            else:
+                # We need to deactivate the currently active component
+                sm = getServiceManager(wrapped_self)
+                old = traverse(sm, 'Packages/'+data[0])
+                old.deactivated()
+
+
+            wrapped_self._data = (cid, ) + tuple(
+                [item for item in data if item != cid]
+                )
+
+            configuration.activated()
+
+        else:
+            raise ValueError(
+                "Configuration to be activated is not registered",
+                configuration)
+    activate = ContextMethod(activate)
+
+    def deactivate(wrapped_self, configuration):
+        cid = wrapped_self._id(configuration)
+
+        if cid in wrapped_self._data:
+
+            if wrapped_self._data[0] != cid:
+                return # already inactive
+
+            # Just stick None on the front
+            wrapped_self._data = (None, ) + wrapped_self._data
+
+            configuration.deactivated()
+
+        else:
+            raise ValueError(
+                "Configuration to be deactivated is not registered",
+                configuration)
+    deactivate = ContextMethod(deactivate)
+
+    def active(wrapped_self):
+        if wrapped_self._data:
+            path = wrapped_self._data[0]
+            if path is not None:
+                # Make sure we can traverse to it.
+                sm = getServiceManager(wrapped_self)
+                configuration = traverse(sm, 'Packages/'+path)
+                return configuration
+
+        return None
+    active = ContextMethod(active)
+
+    def __nonzero__(self):
+        return bool(self._data)
+
+    def info(wrapped_self):
+        sm = getServiceManager(wrapped_self)
+
+        result = [{'id': path,
+                   'active': False,
+                   'configuration': (path and traverse(sm, 'Packages/'+path))
+                   }
+                  for path in wrapped_self._data
+                  ]
+
+        if result:
+            if result[0]['configuration'] is None:
+                del result[0]
+            else:
+                result[0]['active'] = True
+
+        return result
+    info = ContextMethod(info)
+
+
+class SimpleConfiguration(Persistent):
+    """Configuration objects that just contain configuration data
+    """
+
+    __implements__ = IConfiguration, IDeleteNotifiable
+
+    title = description = u''
+
+    def activated(self):
+        pass
+
+    def deactivated(self):
+        pass
+
+    def manage_beforeDelete(self, configuration, container):
+        "See IDeleteNotifiable"
+
+        objectstatus = configuration.status
+
+        if objectstatus == Active:
+            try: objectpath = getPhysicalPathString(configuration)
+            except: objectpath = str(configuration)
+            raise DependencyError("Can't delete active configuration (%s)"
+                                  % objectpath)
+        elif objectstatus == Registered:
+            configuration.status = Unregistered
+
+
+class NamedConfiguration(SimpleConfiguration):
+    """Named configuration
+    """
+
+    __implements__ = INamedConfiguration, SimpleConfiguration.__implements__
+
+    def __init__(self, name, *args, **kw):
+        self.name = name
+        super(NamedConfiguration, self).__init__(*args, **kw)
+
+
+class NamedComponentConfiguration(NamedConfiguration):
+    """Named component configuration
+
+    Subclasses should define a getInterface() method returning the interface
+    of the component.
+    """
+
+    # NamedConfiguration.__implements__ includes IDeleteNotifiable
+    __implements__ = (INamedComponentConfiguration,
+                      NamedConfiguration.__implements__, IAddNotifiable)
+
+    # XXX is all this '*args, **kw' business the right way to use super?
+
+    def __init__(self, name, component_path, permission=None, *args, **kw):
+        self.componentPath = component_path
+        if permission == 'zope.Public':
+            permission = CheckerPublic
+        self.permission = permission
+        super(NamedComponentConfiguration, self).__init__(name, *args, **kw)
+
+    def getComponent(wrapped_self):
+        service_manager = getServiceManager(wrapped_self)
+
+        # We have to be clever here. We need to do an honest to
+        # god unrestricted traveral, which means we have to
+        # traverse from an unproxied object. But, it's not enough
+        # for the service manager to be unproxied, because the
+        # path is an absolute path. When absolute paths are
+        # traversed, the traverser finds the physical root and
+        # traverses from there, so we need to make sure the
+        # physical root isn't proxied.
+
+        # get the root and unproxy it.
+        root = removeAllProxies(getPhysicalRoot(service_manager))
+        component = traverse(root, wrapped_self.componentPath)
+
+        if wrapped_self.permission:
+            if type(component) is Proxy:
+                # XXX what is this?
+                # Answer: There should be at most one security Proxy around
+                # an object. So, if we're going to add a new security proxy,
+                # we need to remove any existing one.
+                component = removeSecurityProxy(component)
+
+            interface = wrapped_self.getInterface()
+
+            checker = InterfaceChecker(interface, wrapped_self.permission)
+
+            component = Proxy(component, checker)
+
+        return component
+    getComponent = ContextMethod(getComponent)
+
+    def manage_afterAdd(self, configuration, container):
+        "See IAddNotifiable"
+        component = configuration.getComponent()
+        dependents = getAdapter(component, IDependable)
+        objectpath = getPhysicalPathString(configuration)
+        dependents.addDependent(objectpath)
+
+    def manage_beforeDelete(self, configuration, container):
+        "See IDeleteNotifiable"
+        super(NamedComponentConfiguration, self
+              ).manage_beforeDelete(configuration, container)
+        component = configuration.getComponent()
+        dependents = getAdapter(component, IDependable)
+        objectpath = getPhysicalPathString(configuration)
+        dependents.removeDependent(objectpath)
+
+
+class NameConfigurable:
+    """Mixin for implementing INameConfigurable
+    """
+
+    __implements__ = INameConfigurable
+
+    def __init__(self, *args, **kw):
+        self._bindings = {}
+        super(NameConfigurable, self).__init__(*args, **kw)
+
+    def queryConfigurationsFor(wrapped_self, cfg, default=None):
+        """See IConfigurable"""
+        return wrapped_self.queryConfigurations(cfg.name, default)
+    queryConfigurationsFor = ContextMethod(queryConfigurationsFor)
+
+    def queryConfigurations(wrapped_self, name, default=None):
+        """See INameConfigurable"""
+        registry = wrapped_self._bindings.get(name, default)
+        return ContextWrapper(registry, wrapped_self)
+    queryConfigurations = ContextMethod(queryConfigurations)
+
+    def createConfigurationsFor(wrapped_self, cfg):
+        """See IConfigurable"""
+        return wrapped_self.createConfigurations(cfg.name)
+    createConfigurationsFor = ContextMethod(createConfigurationsFor)
+
+    def createConfigurations(wrapped_self, name):
+        """See INameConfigurable"""
+        try:
+            registry = wrapped_self._bindings[name]
+        except KeyError:
+            wrapped_self._bindings[name] = registry = ConfigurationRegistry()
+            wrapped_self._p_changed = 1
+        return ContextWrapper(registry, wrapped_self)
+    createConfigurations = ContextMethod(createConfigurations)
+
+    def listConfigurationNames(wrapped_self):
+        """See INameConfigurable"""
+        return filter(wrapped_self._bindings.get,
+                      wrapped_self._bindings.keys())
+
+
+class NameComponentConfigurable(NameConfigurable):
+    """Mixin for implementing INameComponentConfigurable
+    """
+
+    __implements__ = INameComponentConfigurable
+
+    def queryActiveComponent(wrapped_self, name, default=None):
+        """See INameComponentConfigurable"""
+        registry = wrapped_self.queryConfigurations(name)
+        if registry:
+            configuration = registry.active()
+            if configuration is not None:
+                return configuration.getComponent()
+        return default
+    queryActiveComponent = ContextMethod(queryActiveComponent)


=== Zope3/src/zope/app/services/configurationmanager.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:50 2002
+++ Zope3/src/zope/app/services/configurationmanager.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,149 @@
+##############################################################################
+#
+# Copyright (c) 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.
+#
+##############################################################################
+"""
+$Id$
+"""
+
+__metaclass__ = type
+
+from persistence import Persistent
+from zope.app.interfaces.services.configurationmanager import IConfigurationManager
+from zope.app.interfaces.container import IDeleteNotifiable
+from zope.app.interfaces.container import IZopeWriteContainer
+from zope.component import getAdapter
+
+
+class ConfigurationManager(Persistent):
+    """Configuration manager
+
+    Manages configurations within a package.
+    """
+
+    __implements__ = IConfigurationManager, IDeleteNotifiable
+
+    def __init__(self):
+        self._data = ()
+        self._next = 0
+
+    def __getitem__(self, key):
+        "See IItemContainer"
+        v = self.get(key)
+        if v is None:
+            raise KeyError, key
+        return v
+
+    def get(self, key, default=None):
+        "See Interface.Common.Mapping.IReadMapping"
+        for k, v in self._data:
+            if k == key:
+                return v
+        return default
+
+    def __contains__(self, key):
+        "See Interface.Common.Mapping.IReadMapping"
+        return self.get(key) is not None
+
+
+    def keys(self):
+        "See Interface.Common.Mapping.IEnumerableMapping"
+        return [k for k, v in self._data]
+
+    def values(self):
+        "See Interface.Common.Mapping.IEnumerableMapping"
+        return [v for k, v in self._data]
+
+    def items(self):
+        "See Interface.Common.Mapping.IEnumerableMapping"
+        return self._data
+
+    def __len__(self):
+        "See Interface.Common.Mapping.IEnumerableMapping"
+        return len(self._data)
+
+    def setObject(self, key, object):
+        "See IWriteContainer"
+        self._next += 1
+        key = str(self._next)
+        self._data += ((key, object), )
+        return key
+
+    def __delitem__(self, key):
+        "See IWriteContainer"
+        if key not in self:
+            raise KeyError, key
+        self._data = tuple(
+            [item
+             for item in self._data
+             if item[0] != key]
+            )
+
+    def moveTop(self, names):
+        self._data = tuple(
+            [item for item in self._data if (item[0] in names)]
+            +
+            [item for item in self._data if (item[0] not in names)]
+            )
+
+    def moveBottom(self, names):
+        self._data = tuple(
+            [item for item in self._data if (item[0] not in names)]
+            +
+            [item for item in self._data if (item[0] in names)]
+            )
+
+    def _moveUpOrDown(self, names, direction):
+        # Move each named item by one position. Note that this
+        # might require moving some unnamed objects by more than
+        # one position.
+
+        indexes = {}
+
+        # Copy named items to positions one less than they currently have
+        i = -1
+        for item in self._data:
+            i += 1
+            if item[0] in names:
+                j = max(i + direction, 0)
+                while j in indexes:
+                    j += 1
+
+                indexes[j] = item
+
+        # Fill in the rest where there's room.
+        i = 0
+        for item in self._data:
+            if item[0] not in names:
+                while i in indexes:
+                    i += 1
+                indexes[i] = item
+
+        items = indexes.items()
+        items.sort()
+
+        self._data = tuple([item[1] for item in items])
+
+    def moveUp(self, names):
+        self._moveUpOrDown(names, -1)
+
+    def moveDown(self, names):
+        self._moveUpOrDown(names, 1)
+
+    def manage_beforeDelete(self, object, container):
+        assert object == self
+        container = getAdapter(object, IZopeWriteContainer)
+        for k, v in self._data:
+            del container[k]
+
+
+__doc__ = ConfigurationManager.__doc__  + __doc__


=== Zope3/src/zope/app/services/configure.zcml 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:51 2002
+++ Zope3/src/zope/app/services/configure.zcml	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,461 @@
+<zopeConfigure xmlns='http://namespaces.zope.org/zope'>
+
+<!-- Configuration registries -->
+
+  <content class="zope.app.services.configuration.ConfigurationRegistry">
+    <require
+       permission="zope.ManageServices"
+       interface=
+       "zope.app.interfaces.services.configuration.IConfigurationRegistry"
+       />
+    </content>
+
+<!-- Adapter Service -->
+
+  <content class="zope.app.services.adapter.AdapterService">
+    <implements
+       interface="zope.app.interfaces.annotation.IAttributeAnnotatable"
+       />
+    <factory
+       id="zope.app.services.AdapterService"
+       permission="zope.ManageServices"
+       />
+    <require
+       permission="zope.ManageServices"
+       interface="zope.app.interfaces.services.configuration.IConfigurable"
+       attributes="getRegisteredMatching"
+       />
+    </content>
+
+  <content class="zope.app.services.adapter.AdapterConfiguration">
+     <require
+        permission="zope.ManageServices"
+        interface=
+            "zope.app.interfaces.services.interfaces.IAdapterConfiguration"
+        set_schema=
+            "zope.app.interfaces.services.configuration.IConfiguration"
+        />
+     <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IDeleteNotifiable"
+        />
+    </content>    
+
+<!-- View Service -->
+
+  <content class="zope.app.services.view.ViewService">
+    <implements
+       interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+    <factory
+       id="zope.app.services.ViewService"
+       permission="zope.ManageServices"
+       />
+    <require
+       permission="zope.ManageServices"
+       interface="zope.app.interfaces.services.configuration.IConfigurable"
+       attributes="getRegisteredMatching"
+       />
+    </content>
+
+  <content class="zope.app.services.view.ViewConfiguration">
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.services.interfaces.IViewConfiguration"
+        set_schema=
+            "zope.app.interfaces.services.configuration.IConfiguration"
+        />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IDeleteNotifiable"
+        />
+    </content>    
+
+  <content class="zope.app.services.view.PageConfiguration">
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.services.interfaces.IPageConfiguration"
+        set_schema=
+            "zope.app.interfaces.services.configuration.IConfiguration"
+        />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IDeleteNotifiable"
+        />
+    </content>    
+
+<!-- Page Templates -->
+
+  <content class="zope.app.services.zpt.ZPTTemplate">
+    <factory
+        id="zope.app.services.zpt.template"
+        permission="zope.ManageServices"
+        title="ZPT Template"
+        description="Page Template" />
+
+    <require
+        permission="zope.View"
+        attributes="__call__" />
+
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.services.interfaces.IZPTTemplate"
+        set_schema="zope.app.interfaces.services.interfaces.IZPTTemplate" />
+
+    <implements
+        interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+    </content>
+
+<!-- Role Templates -->
+
+  <content class="zope.app.services.role.RoleService">
+    <factory
+        id="RoleService"
+        permission="zope.ManageServices"
+        />
+    <require
+        permission="zope.Security"
+        interface="zope.app.interfaces.security.IRoleService" />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IContainer" />
+    <implements
+        interface="zope.app.interfaces.annotation.IAttributeAnnotatable" /> 
+    </content>
+
+  <content class="zope.app.services.role.Role">
+    <factory />
+    <require
+        permission="zope.Security"
+        interface="zope.app.interfaces.security.IRole" />
+    </content>
+
+<!-- Session Templates -->
+
+  <serviceType
+      id="SessionService" 
+      interface="zope.app.interfaces.services.session.ISessionService" />
+
+  <content class="zope.app.services.session.CookieSessionService">
+    <require
+      permission="zope.Public"
+      interface="zope.app.interfaces.services.session.ISessionService" />
+    <factory
+      id="ISessionService"
+      permission="zope.ManageServices" />
+  <implements
+      interface="zope.app.interfaces.annotation.IAttributeAnnotatable" /> 
+  </content>
+
+<!-- Caching Service -->
+
+  <content class="zope.app.services.cache.CachingService">
+    <factory id="CachingService" permission="zope.ManageServices" />
+      <require
+          permission="zope.View"
+          interface="zope.app.interfaces.cache.cache.ICachingService"
+          attributes="queryConfigurations queryConfigurationsFor
+                      listConfigurationNames" />
+      <require
+          permission="zope.ManageServices"
+          interface="zope.app.interfaces.container.IContainer" />
+      <implements
+          interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+    </content>
+
+  <content class="zope.app.services.cache.CacheConfiguration">
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.services.cache.ICacheConfiguration"
+        set_attributes="name componentPath"
+        set_schema=
+            "zope.app.interfaces.services.configuration.IConfiguration" />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IAddNotifiable" />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IDeleteNotifiable" />
+    </content>
+
+<!-- Service Manager -->
+
+  <content class="zope.app.services.service.ServiceManager">
+    <require
+        permission="zope.View"
+        interface="zope.app.interfaces.container.ISimpleReadContainer" />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.services.service.IServiceManager" />
+    <implements
+        interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+    </content>
+
+  <content class="zope.app.services.service.ServiceConfiguration">
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.services.service.IServiceConfiguration"
+        set_attributes="serviceType componentPath"
+        set_schema=
+            "zope.app.interfaces.services.configuration.IConfiguration"
+        />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IAddNotifiable"
+        />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IDeleteNotifiable"
+        />
+
+  </content>
+
+<!-- Packages -->
+
+  <content class="zope.app.services.package.Packages">
+    <require
+        permission="zope.View"
+        interface="zope.app.interfaces.container.IReadContainer" />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IWriteContainer" />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.services.service.IComponentManager" />
+    <implements
+        interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+
+    </content>
+
+  <content class="zope.app.services.package.Package">
+    <require
+        permission="zope.View"
+        interface="zope.app.interfaces.container.IReadContainer" />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IWriteContainer" />
+    <implements
+        interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+
+    </content>
+
+<!-- Configuration Manager -->
+
+  <content class="zope.app.services.configurationmanager.ConfigurationManager">
+    <factory
+        id = "zope.app.services.service.ServiceManagerConfigurationManager"
+        permission = "zope.ManageServices"
+        title = "Configuration Manager" />
+    <require
+        permission="zope.View"
+        interface="zope.app.interfaces.container.IReadContainer" />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IWriteContainer" />
+    <require
+        permission="zope.ManageServices"
+        interface=
+        "zope.app.interfaces.services.configurationmanager.IOrderedContainer"
+        />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IDeleteNotifiable"
+        />
+    <implements
+        interface="zope.app.interfaces.annotation.IAttributeAnnotatable"
+        />
+    </content>
+
+<!-- Modules -->
+
+  <content class="zope.app.services.module.Manager">
+    <require
+        permission="zope.ManageCode"
+        interface="zodb.code.interfaces.IPersistentModuleManager"
+        />
+    <implements
+        interface="zope.app.interfaces.annotation.IAttributeAnnotatable"
+        />
+    </content>
+
+<!-- View Packages -->
+
+  <content class="zope.app.services.viewpackage.ViewPackage">
+    <factory 
+        id = "zope.app.services.ViewPackage" 
+	permission = "zope.ManageServices"
+	title = "View Package" />
+    <require
+        permission="zope.View"
+        interface="zope.app.interfaces.container.IReadContainer" />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IWriteContainer"
+        set_schema="zope.app.interfaces.services.service.IViewPackageInfo"
+        />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.services.service.IViewPackageInfo"
+        />
+    <implements
+        interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+    </content>
+
+<!-- Connection Service -->
+
+  <content class="zope.app.services.connection.ConnectionService">
+    <factory
+       id="ConnectionService"
+       permission="zope.ManageServices" 
+       />
+    <implements
+       interface="zope.app.interfaces.annotation.IAttributeAnnotatable" 
+       />
+    <require
+        permission="zope.View"
+        interface="zope.app.interfaces.rdb.IConnectionService"
+        attributes="queryConfigurations queryConfigurationsFor
+                    listConfigurationNames" />
+    </content>
+
+  <content class="zope.app.services.connection.ConnectionConfiguration">
+    <require
+        permission="zope.ManageServices"
+        interface=
+        "zope.app.interfaces.services.connection.IConnectionConfiguration"
+        set_attributes="name componentPath"
+        set_schema="zope.app.interfaces.services.configuration.IConfiguration" 
+        />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IAddNotifiable" 
+        />
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IDeleteNotifiable" 
+        />
+    </content>
+
+<!-- Principal annotations (user data) service -->
+
+  <serviceType
+      id="PrincipalAnnotation" 
+      interface="
+   zope.app.interfaces.services.principalannotation.IPrincipalAnnotationService
+      " />
+
+  <content
+     class="zope.app.services.principalannotation.PrincipalAnnotationService"
+     >
+    <require
+        permission="zope.Public"
+        interface="
+  zope.app.interfaces.services.principalannotation.IPrincipalAnnotationService
+        " 
+        />
+    <factory
+        id="IPrincipalAnnotationService"
+        permission="zope.ManageServices" 
+        />
+    <implements 
+       interface="zope.app.interfaces.annotation.IAttributeAnnotatable" 
+       /> 
+    </content>
+
+<!-- Event Service -->
+
+  <content class='zope.app.services.event.LocalEventService'>
+
+    <factory
+        id='Events'
+        permission='zope.ManageServices' />
+
+    <require
+        permission="zope.View"
+        attributes="publish notify" />
+    <require
+        permission="zope.ManageServices"
+        attributes="bound unbound subscribe unsubscribe subscribeOnBind
+                    unsubscribedFrom subscribedTo" />
+    <implements
+        interface="zope.app.interfaces.annotation.IAttributeAnnotatable" 
+        /> 
+    </content>
+
+<!-- Error reporting service -->
+
+  <serviceType
+      id="ErrorReportingService" 
+      interface="zope.app.interfaces.services.error.IErrorReportingService" />
+
+  <content class='zope.app.services.errorr.ErrorReportingService'>
+    <require
+        permission="zope.Public"
+        interface="zope.app.interfaces.services.error.IErrorReportingService" 
+        />
+    <factory
+        id='ErrorReportingService'
+        permission='zope.Public' />
+    <implements 
+        interface="zope.app.interfaces.annotation.IAttributeAnnotatable" 
+        /> 
+    </content>
+
+<!-- Object Hub -->
+
+  <serviceType
+      id='ObjectHub' 
+      interface='zope.app.interfaces.services.hub.IObjectHub' />
+
+  <content class='zope.app.services.hub.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" />
+    <implements 
+       interface="zope.app.interfaces.annotation.IAttributeAnnotatable" /> 
+    </content>
+
+<!-- Authentication Service -->
+
+  <content class="zope.app.services.auth.AuthenticationService">
+ 
+    <factory id="AuthenticationService" permission="zope.ManageServices" />
+
+    <!-- XXX Should you *really* be able to get all this with just the
+         view permission? -->
+    <require
+        permission="zope.View"
+        interface="zope.app.interfaces.security.IAuthenticationService" />
+    <require
+        permission="zope.View"
+        attributes="getAllUsers" />
+
+    <require
+        permission="zope.ManageServices"
+        interface="zope.app.interfaces.container.IContainer" />
+
+    <implements
+       interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+
+    </content>
+
+  <content class="zope.app.services.auth.User">
+    <factory id="User" permission="zope.ManageServices" />
+      <require
+          permission="zope.View"
+          interface="zope.app.interfaces.services.auth.IReadUser" />
+      <require
+          permission="zope.ManageContent"
+          interface="zope.app.interfaces.services.auth.IWriteUser" />
+      <implements
+          interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+    </content>
+
+</zopeConfigure>


=== Zope3/src/zope/app/services/connection.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:51 2002
+++ Zope3/src/zope/app/services/connection.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,99 @@
+##############################################################################
+#
+# Copyright (c) 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.
+#
+##############################################################################
+"""
+$Id$
+"""
+
+from persistence import Persistent
+from zope.proxy.context import ContextMethod
+from zope.app.component.nextservice import queryNextService
+from zope.app.interfaces.services.configuration \
+        import INameComponentConfigurable
+from zope.app.services.configuration import NameComponentConfigurable
+from zope.app.interfaces.rdb import IConnectionService
+
+
+class ILocalConnectionService(IConnectionService, INameComponentConfigurable):
+    """A local (placeful) connection service"""
+
+
+class ConnectionService(Persistent, NameComponentConfigurable):
+
+    __doc__ = ILocalConnectionService.__doc__
+
+    __implements__ = ILocalConnectionService
+
+    def __init__(self):
+        super(ConnectionService, self).__init__()
+
+    def getConnection(self, name):
+        'See IConnectionService'
+        adapter = self.queryActiveComponent(name)
+        if adapter is not None:
+            return adapter()
+        service = queryNextService(self, "SQLDatabaseConnections")
+        if service is not None:
+            return service.getConnection(name)
+        raise KeyError, name
+
+    getConnection = ContextMethod(getConnection)
+
+    def queryConnection(self, name, default=None):
+        'See IConnectionService'
+        try:
+            return self.getConnection(name)
+        except KeyError:
+            return default
+
+    queryConnection = ContextMethod(queryConnection)
+
+    def getAvailableConnections(self):
+        'See IConnectionService'
+        connections = {}
+        for name in self.listConfigurationNames():
+            registry = self.queryConfigurations(name)
+            if registry.active() is not None:
+                connections[name] = 0
+        service = queryNextService(self, "SQLDatabaseConnections")
+        if service is not None:
+            for name in service.getAvailableConnections():
+                connections[name] = 0
+        return connections.keys()
+
+    getAvailableConnections = ContextMethod(getAvailableConnections)
+
+
+
+"""A configuration for a database adapter.
+
+$Id$
+"""
+
+from zope.app.interfaces.services.connection import IConnectionConfiguration
+from zope.app.services.configuration import NamedComponentConfiguration
+from zope.app.services.configuration import ConfigurationStatusProperty
+
+class ConnectionConfiguration(NamedComponentConfiguration):
+
+    __doc__ = IConnectionConfiguration.__doc__
+
+    __implements__ = (IConnectionConfiguration,
+                      NamedComponentConfiguration.__implements__)
+
+    status = ConfigurationStatusProperty('SQLDatabaseConnections')
+
+    label = "Connection"
+
+    def __init__(self, *args, **kw):
+        super(ConnectionConfiguration, self).__init__(*args, **kw)


=== Zope3/src/zope/app/services/errorr.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:51 2002
+++ Zope3/src/zope/app/services/errorr.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,208 @@
+##############################################################################
+#
+# 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$
+"""
+
+import time
+from random import random
+from thread import allocate_lock
+from persistence import Persistent
+from types import StringTypes
+import logging
+from zope.exceptions.exceptionformatter import format_exception
+from zope.proxy.context import ContextMethod
+from zope.app.interfaces.services.error \
+        import IErrorReportingService
+
+#Restrict the rate at which errors are sent to the Event Log
+_rate_restrict_pool = {}
+
+# The number of seconds that must elapse on average between sending two
+# exceptions of the same name into the the Event Log. one per minute.
+_rate_restrict_period = 60
+
+# The number of exceptions to allow in a burst before the above limit
+# kicks in. We allow five exceptions, before limiting them to one per
+# minute.
+_rate_restrict_burst = 5
+
+# _temp_logs holds the logs.
+_temp_logs = {}  # { oid -> [ traceback string ] }
+
+cleanup_lock = allocate_lock()
+
+class ErrorReportingService(Persistent):
+    """Error Reporting Service
+    """
+    __implements__ = IErrorReportingService
+
+    keep_entries = 20
+    copy_to_zlog = 0
+    _ignored_exceptions = ('Unauthorized',)
+
+
+    def _getLog(self):
+        """Returns the log for this object.
+        Careful, the log is shared between threads.
+        """
+        log = _temp_logs.get(self._p_oid, None)
+        if log is None:
+            log = []
+            _temp_logs[self._p_oid] = log
+        return log
+
+    # Exceptions that happen all the time, so we dont need
+    # to log them. Eventually this should be configured
+    # through-the-web.
+    def raising(self, info, request=None):
+        """Log an exception.
+        Called by ZopePublication.handleException method.
+        """
+        now = time.time()
+        try:
+            tb_text = None
+            tb_html = None
+
+            strtype = str(getattr(info[0], '__name__', info[0]))
+            if strtype in self._ignored_exceptions:
+                return
+
+            if not isinstance(info[2], StringTypes):
+                tb_text = ''.join(
+                        format_exception(*info, **{'as_html': 0}))
+                tb_html = ''.join(
+                    format_exception(*info, **{'as_html': 1}))
+            else:
+                tb_text = info[2]
+
+            url = None
+            username = None
+            req_html = None
+            if request:
+                url = request.URL
+                try:
+                    username = ', '.join((request.user.getLogin(),
+                                          request.user.getId(),
+                                          request.user.getTitle(),
+                                          request.user.getDescription()
+                                         ))
+                # When there's an unauthorized access, request.user is
+                # not set, so we get an AttributeError
+                # XXX is this right? Surely request.user should be set!
+                except AttributeError:
+                    pass
+
+                req_html = ''.join(['%s : %s<br>' % item
+                                    for item in request.items()])
+            try:
+                strv = str(info[1])
+            # A call to str(obj) could raise anything at all.
+            # We'll ignore these errors, and print something
+            # useful instead, but also log the error.
+            except:
+                logging.getLogger('SiteError').exception(
+                    'Error in ErrorReportingService while getting a str '
+                    'representation of an object')
+                strv = '<unprintable %s object>' % (
+                        str(type(info[1]).__name__)
+                        )
+
+            log = self._getLog()
+            entry_id = str(now) + str(random()) # Low chance of collision
+
+            log.append({
+                'type': strtype,
+                'value': strv,
+                'time': time.ctime(now),
+                'id': entry_id,
+                'tb_text': tb_text,
+                'tb_html': tb_html,
+                'username': username,
+                'url': url,
+                'req_html': req_html,
+                })
+            cleanup_lock.acquire()
+            try:
+                if len(log) >= self.keep_entries:
+                    del log[:-self.keep_entries]
+            finally:
+                cleanup_lock.release()
+
+            if self.copy_to_zlog:
+                self._do_copy_to_zlog(now, strtype, str(url), info)
+        finally:
+            info = None
+    raising = ContextMethod(raising)
+
+    def _do_copy_to_zlog(self, now, strtype, url, info):
+        # XXX info is unused; logging.exception() will call sys.exc_info()
+        when = _rate_restrict_pool.get(strtype,0)
+        if now > when:
+            next_when = max(when,
+                            now - _rate_restrict_burst*_rate_restrict_period)
+            next_when += _rate_restrict_period
+            _rate_restrict_pool[strtype] = next_when
+            logging.getLogger('SiteError').exception(str(url))
+
+    def getProperties(self):
+        return {
+            'keep_entries': self.keep_entries,
+            'copy_to_zlog': self.copy_to_zlog,
+            'ignored_exceptions': self._ignored_exceptions,
+            }
+    getProperties = ContextMethod(getProperties)
+
+    def setProperties(self, keep_entries, copy_to_zlog=0,
+                      ignored_exceptions=()):
+        """Sets the properties of this site error log.
+        """
+        copy_to_zlog = bool(copy_to_zlog)
+        self.keep_entries = int(keep_entries)
+        self.copy_to_zlog = copy_to_zlog
+        self._ignored_exceptions = tuple(
+                filter(None, map(str, ignored_exceptions))
+                )
+    setProperties = ContextMethod(setProperties)
+    def getLogEntries(self):
+        """Returns the entries in the log, most recent first.
+
+        Makes a copy to prevent changes.
+        """
+        res = [entry.copy() for entry in self._getLog()]
+        res.reverse()
+        return res
+    getLogEntries = ContextMethod(getLogEntries)
+
+    def getLogEntryById(self, id):
+        """Returns the specified log entry.
+        Makes a copy to prevent changes.  Returns None if not found.
+        """
+        for entry in self._getLog():
+            if entry['id'] == id:
+                return entry.copy()
+        return None
+    getLogEntryById = ContextMethod(getLogEntryById)
+
+
+def _cleanup_temp_log():
+    _temp_logs.clear()
+
+_clear = _cleanup_temp_log
+# Register our cleanup with Testing.CleanUp to make writing unit tests simpler.
+from zope.testing.cleanup import addCleanUp
+addCleanUp(_clear)
+del addCleanUp


=== Zope3/src/zope/app/services/event.py 1.1 => 1.2 === (476/576 lines abridged)
--- /dev/null	Wed Dec 25 09:13:51 2002
+++ Zope3/src/zope/app/services/event.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,573 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Event service implementation.
+
+$Id$
+"""
+
+from persistence import Persistent
+
+from zope.app.component.nextservice import getNextService
+from zope.app.event.globaleventservice import eventService
+from zope.app.interfaces.services.event import IPathSubscriber
+from zope.app.interfaces.traversing.traverser import ITraverser
+from zope.app.traversing import getPhysicalPathString
+from zope.component import getAdapter
+from zope.component import getService
+from zope.event.subscribable import Subscribable
+from zope.exceptions import NotFoundError
+from zope.interface import Attribute
+from zope.interfaces.event import IEvent
+from zope.interfaces.event import IEventChannel
+from zope.interfaces.event import IEventService
+from zope.interfaces.event import ISubscriptionAware
+from zope.proxy.context import ContextWrapper
+from zope.proxy.context import isWrapper
+from zope.proxy.context import ContextMethod
+from zope.proxy.introspection import removeAllProxies
+
+from zope.app.component.nextservice import getNextService, queryNextService
+from zope.app.interfaces.services.service import IBindingAware
+from zope.app.traversing import getPhysicalPathString, traverse
+
+
+class LocalSubscribable(Persistent, Subscribable):
+    """a local mix-in"""
+

[-=- -=- -=- 476 lines omitted -=- -=- -=-]

+                # 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)
+
+
+class AbstractIndirectSubscriber:
+
+    def notify(wrapped_self, event):
+        removeAllProxies(wrapped_self)._getSubscriber(
+            wrapped_self).notify(event)
+
+    notify=ContextMethod(notify)
+
+    def subscribedTo(wrapped_self, subscribable, event_type, filter):
+        proxiedObj = removeAllProxies(
+            wrapped_self)._getSubscriber(wrapped_self)
+        if ISubscriptionAware.isImplementedBy(
+            removeAllProxies(proxiedObj)):
+            proxiedObj.subscribedTo(
+                subscribable, event_type, filter )
+
+    subscribedTo=ContextMethod(subscribedTo)
+
+    def unsubscribedFrom(wrapped_self, subscribable, event_type, filter):
+        proxiedObj = removeAllProxies(
+            wrapped_self)._getSubscriber(wrapped_self)
+        if ISubscriptionAware.isImplementedBy(
+            removeAllProxies(proxiedObj)):
+            proxiedObj.unsubscribedFrom(
+                subscribable, event_type, filter )
+
+    unsubscribedFrom=ContextMethod(unsubscribedFrom)
+
+
+class PathSubscriber(AbstractIndirectSubscriber):
+
+    __implements__ = IPathSubscriber, ISubscriptionAware
+
+    def __init__(self, wrapped_subscriber):
+        self.subscriber_path = getPhysicalPathString(wrapped_subscriber)
+
+    def __eq__(self, other):
+        return (IPathSubscriber.isImplementedBy(other) and
+               other.subscriber_path == self.subscriber_path)
+
+    def _getSubscriber(self, wrapped_self):
+        return traverse(wrapped_self, self.subscriber_path)


=== Zope3/src/zope/app/services/field.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:51 2002
+++ Zope3/src/zope/app/services/field.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,59 @@
+##############################################################################
+#
+# Copyright (c) 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.
+#
+##############################################################################
+"""Component location field.
+
+$Id$
+"""
+__metaclass__ = type
+
+from zope.schema.interfaces import IField
+from zope.schema import Field
+from zope.schema.interfaces import ValidationError
+from zope.app.traversing import traverse
+from zope.app.component.interfacefield import InterfaceField
+from zope.exceptions import NotFoundError
+
+class IComponentLocation(IField):
+    """A field containing a component path.
+    """
+
+    type = InterfaceField(
+        title = u"An interface that must be implemented by the component.",
+        required = True,
+        readonly = True,
+        )
+
+class ComponentLocation(Field):
+
+    __implements__ = IComponentLocation
+
+    _type = unicode
+
+    def __init__(self, type, *args, **kw):
+        self.type = type
+        super(ComponentLocation, self).__init__(*args, **kw)
+
+    def _validate(self, value):
+        super(ComponentLocation, self)._validate(value)
+
+        if not value.startswith('/'):
+            raise ValidationError("Not an absolute path", value)
+
+        try:
+            component = traverse(self.context, value)
+        except NotFoundError:
+            raise ValidationError("Path for non-existent object", value)
+
+        if not self.type.isImplementedBy(component):
+            raise ValidationError("Wrong component type")


=== Zope3/src/zope/app/services/hub.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:51 2002
+++ Zope3/src/zope/app/services/hub.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,402 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Object hub implementation.
+
+$Id$
+"""
+
+from __future__ import generators
+
+__metaclass__ = type
+
+import random
+
+from zodb.btrees.IOBTree import IOBTree
+from zodb.btrees.OIBTree import OIBTree
+
+from zope.app.traversing import getPhysicalPath
+from zope.app.traversing import locationAsTuple, locationAsUnicode
+from zope.component import getAdapter
+from zope.exceptions import NotFoundError
+from zope.proxy.context import ContextWrapper
+from zope.proxy.context import isWrapper
+from zope.proxy.context import ContextMethod
+from zope.proxy.introspection import removeAllProxies
+
+from zope.app.interfaces.traversing.traverser import ITraverser
+from zope.app.interfaces.event import IObjectRemovedEvent, IObjectEvent
+from zope.app.interfaces.event import IObjectMovedEvent, IObjectCreatedEvent
+from zope.app.interfaces.event import IObjectModifiedEvent
+from zope.app.interfaces.services.hub import IObjectHub, ObjectHubError
+from zope.app.services.event import ProtoServiceEventChannel
+from zope.app.interfaces.services.hub import IObjectRegisteredHubEvent
+from zope.app.interfaces.services.hub import IObjectUnregisteredHubEvent
+from zope.app.interfaces.services.hub import IObjectModifiedHubEvent
+from zope.app.interfaces.services.hub import IObjectMovedHubEvent
+from zope.app.interfaces.services.hub import IObjectRemovedHubEvent
+from zope.app.interfaces.traversing.traverser import ITraverser
+
+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
+
+
+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)
+        # int --> tuple of unicodes
+        self.__hubid_to_location = IOBTree()
+        # tuple of unicodes --> int
+        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 = locationAsTuple(event.fromLocation)
+                hubid = clean_self.__location_to_hubid.get(canonical_location)
+                if hubid is not None:
+                    canonical_new_location = locationAsTuple(
+                        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'
+                            % locationAsUnicode(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)
+            elif IObjectCreatedEvent.isImplementedBy(event):
+                # a newly created object that has not been added to a
+                # container yet has no location. So, we're not interested in
+                # it.
+                pass
+            else:
+                canonical_location = locationAsTuple(event.location)
+                hubid = clean_self.__location_to_hubid.get(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 = getPhysicalPath(location)
+        else:
+            location = locationAsTuple(location)
+        hubid = self.__location_to_hubid.get(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(wrapped_self, hubid):
+        '''See interface IObjectHub'''
+        location = wrapped_self.getLocation(hubid)
+        adapter = getAdapter(wrapped_self, ITraverser)
+        return adapter.traverse(location)
+    getObject = ContextMethod(getObject)
+
+    def register(wrapped_self, obj_or_loc):
+        '''See interface ILocalObjectHub'''
+        clean_self = removeAllProxies(wrapped_self)
+        # XXX Need a new unit test for this; previously we tested
+        #     whether it's wrapped, which is wrong because the root
+        #     isn't wrapped (and it certainly makes sense to want to
+        #     register the root).
+        if isinstance(obj_or_loc, (str, unicode, tuple)):
+            obj = None
+            location = obj_or_loc
+        else:
+            obj = obj_or_loc
+            location = getPhysicalPath(obj_or_loc)
+        canonical_location = locationAsTuple(location)
+        if not canonical_location[0] == u'':
+            raise ValueError("Location must be absolute")
+
+        # This is here to make sure the 'registrations' method won't
+        # trip up on using unichar ffff as a sentinel.
+        for segment in canonical_location:
+            if segment.startswith(u'\uffff'):
+                raise ValueError(
+                    "Location contains a segment starting with \\uffff")
+
+        location_to_hubid = clean_self.__location_to_hubid
+        if location_to_hubid.has_key(canonical_location):
+            # XXX It would be more convenient if register() returned
+            #     a bool indicating whether the object is already
+            #     registered, rather than raising an exception.
+            #     Then a more useful distinction between real errors
+            #     and this (common) condition could be made.
+            raise ObjectHubError(
+                'location %s already in object hub' %
+                locationAsUnicode(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 = getPhysicalPath(location) # XXX this branch is
+            # not exercised: needs unit test
+            canonical_location = locationAsTuple(location)
+        elif isinstance(location, int):
+            canonical_location = clean_self.getLocation(location)
+        else:
+            canonical_location = locationAsTuple(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' %
+                locationAsUnicode(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 numRegistrations(self):
+        """See interface IObjectHub"""
+        # The hubid<-->location mappings should be the same size.
+        # The IOBTree of hubid-->location might be faster to find the
+        # size of, as the keys are ints. But, I haven't tested that.
+        # assert len(self.__hubid_to_location)==len(self.__location_to_hubid)
+        return len(self.__hubid_to_location)
+
+    def getRegistrations(self, location=(u'',)):
+        """See interface IObjectHub"""
+        # Location can be an ascii string a unicode or a tuple of strings
+        # or unicodes. So, get a canonical location first of all.
+        location = locationAsTuple(location)
+        if location == (u'',):
+            # Optimisation when we're asked for all the registered objects.
+            # Returns an IOBTreeItems object.
+            return self.__location_to_hubid.items()
+
+        # BTrees only support searches including the min and max.
+        # So, I need to add to the end of the location a string that will
+        # be larger than any other. I could also use a type that
+        # sorts after unicodes.
+        return self.__location_to_hubid.items(location, location+(u'\uffff',))
+
+    def iterObjectRegistrations(wrapped_self):
+        """See interface IHubEventChannel"""
+        traverser = getAdapter(wrapped_self, ITraverser)
+        for location, hubId in wrapped_self.getRegistrations():
+            yield (location, hubId, traverser.traverse(location))
+    iterObjectRegistrations = ContextMethod(iterObjectRegistrations)
+
+    ############################################################
+
+    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


=== Zope3/src/zope/app/services/hubcollaborations.txt 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:51 2002
+++ Zope3/src/zope/app/services/hubcollaborations.txt	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,187 @@
+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"
+


=== Zope3/src/zope/app/services/module.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zope/app/services/module.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,85 @@
+##############################################################################
+#
+# Copyright (c) 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.
+#
+##############################################################################
+"""Manager for persistent modules associated with a service manager.
+
+$Id$
+"""
+
+from persistence import Persistent
+from zodb.code.module import PersistentModuleManager
+from zodb.code.interfaces import IPersistentModuleManager
+
+from zope.component import getServiceManager
+from zope.proxy.context import ContextMethod
+
+class Registry:
+
+    # The registry is found via context, but the PersistentModuleManager
+    # doesn't know about context.  To make it behave contextually, this
+    # Registry class collaborates with Manager to delegate to the
+    # registry found via context.
+
+    def __init__(self):
+        self._v_manager = None
+
+    def setManager(self, ctx):
+        self._v_manager = ctx
+
+    def findModule(self, name):
+        return self._v_manager.findModule(name)
+
+    def setModule(self, name, module):
+        return self._v_manager.setModule(name, module)
+
+    def delModule(self, name):
+        return self._v_manager.delModule(name)
+
+class Manager(Persistent):
+
+    __implements__ = IPersistentModuleManager
+
+    # The registry for the manager is the ServiceManager.
+    # The association between this manager and the registry
+    # is static, but the static association can't be stored
+    # explicitly in Zope.
+
+    # XXX There is no locking, but every call to setManager() for a
+    # particular instance should have the same manager argument.
+
+    # XXX It would be nice if the lookup via getServiceManager()
+    # occurred less often.  Best would be to do it only when the
+    # object is unpickled.
+
+    def __init__(self):
+        self._registry = Registry()
+        self._manager = PersistentModuleManager(self._registry)
+
+    def new(self, name, source):
+        self._registry.setManager(getServiceManager(self))
+        self._manager.new(name, source)
+
+    def update(self, source):
+        self._registry.setManager(getServiceManager(self))
+        self._manager.update(source)
+
+    def remove(self, source):
+        self._registry.setManager(getServiceManager(self))
+        self._manager.remove(source)
+
+    new = ContextMethod(new)
+    update = ContextMethod(update)
+    remove = ContextMethod(remove)
+
+    name = property(lambda self: self._manager.name)
+    source = property(lambda self: self._manager.source)


=== Zope3/src/zope/app/services/package.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zope/app/services/package.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,100 @@
+##############################################################################
+#
+# Copyright (c) 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.
+#
+##############################################################################
+"""XXX short summary goes here.
+
+XXX longer description goes here.
+
+$Id$
+"""
+__metaclass__ = type
+
+
+from zope.app.container.btree import BTreeContainer
+from zope.proxy.context import ContextMethod
+from zope.proxy.context import ContextWrapper
+from zope.app.traversing import getPhysicalPathString
+from zope.app.component.nextservice import getNextServiceManager
+from zope.app.interfaces.services.service \
+     import IServiceManager
+from zope.app.interfaces.services.service \
+     import IComponentManager
+
+from zope.app.interfaces.services.package import IPackages
+from zope.app.interfaces.services.package import IPackage
+
+
+class Packages(BTreeContainer):
+    __implements__ = IPackages
+
+    def __init__(self):
+        super(Packages, self).__init__()
+        self.setObject('default', Package())
+
+    def queryComponent(self, type=None, filter=None, all=0):
+
+        local = []
+        path = getPhysicalPathString(self)
+        for package_name in self:
+            package = ContextWrapper(self[package_name], self,
+                                     name=package_name)
+            for name in package:
+                component = package[name]
+                if type is not None and not type.isImplementedBy(component):
+                    continue
+                if filter is not None and not filter(component):
+                    continue
+                local.append({'path': "%s/%s/%s" % (path, package_name, name),
+                              'component': ContextWrapper(component, package,
+                                                          name=name),
+                              })
+
+        if all:
+            next_service_manager = getNextServiceManager(self)
+            if IComponentManager.isImplementedBy(next_service_manager):
+                next_service_manager.queryComponent(type, filter, all)
+
+            local += list(all)
+
+        return local
+
+    queryComponent = ContextMethod(queryComponent)
+
+    def setObject(self, name, object):
+        if not IPackage.isImplementedBy(object):
+            raise TypeError("Can only add packages")
+        return super(Packages, self).setObject(name, object)
+
+
+
+
+"""XXX short summary goes here.
+
+XXX longer description goes here.
+
+$Id$
+"""
+__metaclass__ = type
+
+from zope.app.container.btree import BTreeContainer
+
+from zope.app.interfaces.services.package import IPackage
+from zope.app.services.configurationmanager import ConfigurationManager
+
+
+class Package(BTreeContainer):
+    __implements__ = IPackage
+
+    def __init__(self):
+        super(Package, self).__init__()
+        self.setObject('configure', ConfigurationManager())


=== Zope3/src/zope/app/services/principalannotation.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zope/app/services/principalannotation.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,107 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+"""Implementation of IPrincipalAnnotationService."""
+
+# TODO: register service as adapter for IAnnotations on service activation
+# this depends on existence of LocalAdapterService, so once that's done
+# implement this.
+
+# Zope3 imports
+from persistence import Persistent
+from zodb.btrees.OOBTree import OOBTree
+from zope.app.component.nextservice import getNextService
+from zope.proxy.context import ContextMethod
+from zope.proxy.context import ContextWrapper
+from zope.app.interfaces.annotation import IAnnotations
+
+# Sibling imports
+from zope.app.interfaces.services.principalannotation import IPrincipalAnnotationService
+
+
+class PrincipalAnnotationService(Persistent):
+    """Stores IAnnotations for IPrinicipals.
+
+    The service ID is 'PrincipalAnnotation'.
+    """
+
+    __implements__ = IPrincipalAnnotationService, Persistent.__implements__
+
+    def __init__(self):
+        self.annotations = OOBTree()
+
+
+    # implementation of IPrincipalAnnotationService
+
+    def getAnnotation(self, principalId):
+        """Return object implementing IAnnotations for the givin principal.
+
+        If there is no IAnnotations it will be created and then returned.
+        """
+        if not self.annotations.has_key(principalId):
+            self.annotations[principalId] = Annotations(principalId)
+        return ContextWrapper(self.annotations[principalId], self, name=principalId)
+
+    getAnnotation = ContextMethod(getAnnotation)
+
+    def hasAnnotation(self, principalId):
+        """Return boolean indicating if given principal has IAnnotations."""
+        return self.annotations.has_key(principalId)
+
+
+class Annotations(Persistent):
+    """Stores annotations."""
+
+    __implements__ = IAnnotations, Persistent.__implements__
+
+    def __init__(self, principalId):
+        self.principalId = principalId
+        self.data = OOBTree()
+
+    def __getitem__(wrapped_self, key):
+        try:
+            return wrapped_self.data[key]
+        except KeyError:
+            # We failed locally: delegate to a higher-level service.
+            service = getNextService(wrapped_self, 'PrincipalAnnotation')
+            if service:
+                return service.getAnnotation(wrapped_self.principalId)[key]
+            raise
+
+    __getitem__ = ContextMethod(__getitem__)
+
+    def __setitem__(self, key, value):
+        self.data[key] = value
+
+    def __delitem__(self, key):
+        del self.data[key]
+
+    def get(self, key, default=None):
+        try:
+            return self.data[key]
+        except KeyError:
+            return default
+
+
+class AnnotationsForPrincipal(object):
+    """Adapter from IPrincipal to IAnnotations for a PrincipalAnnotationService.
+
+    Register an *instance* of this class as an adapter.
+    """
+
+    def __init__(self, service):
+        self.service = service
+
+    def __call__(self, principal):
+        return self.service.getAnnotation(principal.getId())


=== Zope3/src/zope/app/services/role.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zope/app/services/role.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,69 @@
+##############################################################################
+#
+# 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.app.security.registries.roleregistry import Role
+from persistence import Persistent
+
+class Role(Role, Persistent):
+    "Persistent Role"
+
+
+
+
+"""
+
+Revision information:
+$Id$
+"""
+from zope.app.container.btree import BTreeContainer
+from zope.app.interfaces.security import IRoleService
+from zope.app.interfaces.container import IContainer
+from zope.proxy.context import ContextMethod
+from zope.app.component.nextservice import getNextService
+
+class ILocalRoleService(IRoleService, IContainer):
+    """TTW manageable role service"""
+
+class RoleService(BTreeContainer):
+
+    __implements__ = ILocalRoleService
+
+    def getRole(wrapped_self, rid):
+        '''See interface IRoleService'''
+        try:
+            return wrapped_self[rid]
+        except KeyError:
+            # We failed locally: delegate to a higher-level service.
+            sv = getNextService(wrapped_self, 'Roles')
+            if sv:
+                return sv.getRole(rid)
+            raise # will be original Key Error
+    getRole = ContextMethod(getRole)
+
+    def getRoles(wrapped_self):
+        '''See interface IRoleService'''
+        roles = list(wrapped_self.values())
+        roleserv = getNextService(wrapped_self, 'Roles')
+        if roleserv:
+            roles.extend(roleserv.getRoles())
+        return roles
+    getRoles = ContextMethod(getRoles)
+
+    #
+    ############################################################


=== Zope3/src/zope/app/services/service.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zope/app/services/service.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,308 @@
+##############################################################################
+#
+# 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.app.interfaces.services.service import IServiceManagerContainer
+from zope.component.interfaces import IServiceService
+from zope.component.exceptions import ComponentLookupError
+
+_marker = object()
+
+class ServiceManagerContainer:
+
+    __implements__ =  IServiceManagerContainer
+
+    def hasServiceManager(self):
+        '''See interface IReadServiceManagerContainer'''
+        return hasattr(self, '_ServiceManagerContainer__sm')
+
+    def getServiceManager(self):
+        '''See interface IReadServiceManagerContainer'''
+
+        try:
+            return self.__sm
+        except AttributeError:
+            raise ComponentLookupError('no service manager defined')
+
+    def queryServiceManager(self, default=None):
+        '''See interface IReadServiceManagerContainer'''
+
+        return getattr(self, '_ServiceManagerContainer__sm', default)
+
+    def setServiceManager(self, sm):
+        '''See interface IWriteServiceManagerContainer'''
+
+        if IServiceService.isImplementedBy(sm):
+            self.__sm = sm
+        else:
+            raise ValueError('setServiceManager requires an IServiceService')
+
+    #
+    ############################################################
+
+
+
+"""
+$Id$
+"""
+
+from zope.app.interfaces.services.service import IServiceConfiguration
+from zope.app.interfaces.services.service import IBindingAware
+from zope.app.services.configuration import ConfigurationStatusProperty
+from zope.app.services.configuration import NamedComponentConfiguration
+from zope.proxy.context import ContextMethod
+from zope.component import getServiceManager
+
+class ServiceConfiguration(NamedComponentConfiguration):
+
+    __doc__ = IServiceConfiguration.__doc__
+
+    __implements__ = (IServiceConfiguration,
+                      NamedComponentConfiguration.__implements__)
+
+    status = ConfigurationStatusProperty('Services')
+
+    label = "Service"
+
+    def __init__(self, *args, **kw):
+        super(ServiceConfiguration, self).__init__(*args, **kw)
+
+    def getInterface(self):
+        service_manager = getServiceManager(self)
+        return service_manager.getInterfaceFor(self.name)
+
+    getInterface = ContextMethod(getInterface)
+
+    def activated(self):
+        service = self.getComponent()
+        if IBindingAware.isImplementedBy(service):
+            service.bound(self.name)
+
+    activated = ContextMethod(activated)
+
+    def deactivated(self):
+        service = self.getComponent()
+        if IBindingAware.isImplementedBy(service):
+            service.unbound(self.name)
+
+    deactivated = ContextMethod(deactivated)
+
+__doc__ = ServiceConfiguration.__doc__  + __doc__
+
+
+
+
+"""XXX I need a summary line.
+
+In addition, a ServiceManager acts as a registry for persistent
+modules.  The Zope import hook uses the ServiceManager to search for
+modules.
+
+$Id$
+"""
+
+import sys
+
+from zope.app.component.nextservice \
+     import getNextServiceManager, getNextService
+from zope.component.exceptions import ComponentLookupError
+
+from zope.app.interfaces.container import ISimpleReadContainer
+from zope.proxy.context import ContextMethod
+from zope.proxy.context import ContextWrapper
+from zope.proxy.introspection import removeAllProxies
+
+from zope.app.services.package import Packages
+from zope.app.interfaces.services.service import IServiceManager
+
+from zope.app.services.configuration import NameComponentConfigurable
+
+from zodb.code.module import PersistentModuleRegistry
+from zodb.code.module import PersistentModule
+from zope.app.interfaces.services.service import INameResolver
+
+ModuleType = type(INameResolver)
+ModuleType = ModuleType, PersistentModule
+
+
+class ServiceManager(PersistentModuleRegistry, NameComponentConfigurable):
+
+    __implements__ = (IServiceManager, ISimpleReadContainer,
+                      PersistentModuleRegistry.__implements__,
+                      NameComponentConfigurable.__implements__,
+                      INameResolver)
+
+    def __init__(self):
+        super(ServiceManager, self).__init__()
+        NameComponentConfigurable.__init__(self)
+        self.Packages = Packages()
+
+    def getServiceDefinitions(wrapped_self):
+        "See IServiceService"
+
+        # Get the services defined here and above us, if any (as held
+        # in a ServiceInterfaceService, presumably)
+        sm = getNextServiceManager(wrapped_self)
+        if sm is not None:
+            serviceDefs = sm.getServiceDefinitions()
+        else: serviceDefs = {}
+
+        return serviceDefs
+    getServiceDefinitions = ContextMethod(getServiceDefinitions)
+
+    def queryService(wrapped_self, name, default=None):
+        "See IServiceService"
+        try:
+            return wrapped_self.getService(name)
+        except ComponentLookupError:
+            return default
+    queryService = ContextMethod(queryService)
+
+    def getService(wrapped_self, name):
+        "See IServiceService"
+
+        # This is rather tricky. Normally, getting a service requires
+        # the use of other services, like the adapter service.  We
+        # need to be careful not to get into an infinate recursion by
+        # getting out getService to be called while looking up
+        # services, so we'll
+
+        if name == 'Services':
+            return wrapped_self # We are the service service
+
+        if not getattr(wrapped_self, '_v_calling', 0):
+
+            wrapped_self._v_calling = 1
+            try:
+                service = wrapped_self.queryActiveComponent(name)
+                if service is not None:
+                    return service
+
+            finally:
+                wrapped_self._v_calling = 0
+
+        return getNextService(wrapped_self, name)
+    getService = ContextMethod(getService)
+
+
+    def getInterfaceFor(wrapped_self, service_type):
+        "See IServiceService"
+        for type, interface in wrapped_self.getServiceDefinitions():
+            if type == service_type:
+                return interface
+
+        raise NameError(service_type)
+    getInterfaceFor = ContextMethod(getInterfaceFor)
+
+    def queryComponent(wrapped_self, type=None, filter=None, all=0):
+        Packages = ContextWrapper(wrapped_self.Packages, wrapped_self,
+                                  name='Packages')
+        return Packages.queryComponent(type, filter, all)
+    queryComponent = ContextMethod(queryComponent)
+
+
+    # We provide a mapping interface for traversal, but we only expose
+    # local services through the mapping interface.
+
+    def __getitem__(self, key):
+        "See Interface.Common.Mapping.IReadMapping"
+
+        result = self.get(key)
+        if result is None:
+            raise KeyError(key)
+
+        return result
+
+    def get(wrapped_self, key, default=None):
+        "See Interface.Common.Mapping.IReadMapping"
+
+        if key == 'Packages':
+            return wrapped_self.Packages
+
+        service = wrapped_self.queryActiveComponent(key)
+        if service is None:
+            return default
+
+        return service
+
+    get = ContextMethod(get)
+
+    def __contains__(self, key):
+        "See Interface.Common.Mapping.IReadMapping"
+
+        return self.get(key) is not None
+
+    def findModule(wrapped_self, name):
+        # override to pass call up to next service manager
+        mod = super(ServiceManager,
+                    removeAllProxies(wrapped_self)).findModule(name)
+        if mod is not None:
+            return mod
+
+        sm = getNextServiceManager(wrapped_self)
+        try:
+            findModule = sm.findModule
+        except AttributeError:
+            # The only service manager that doesn't implement this
+            # interface is the global service manager.  There is no
+            # direct way to ask if sm is the global service manager.
+            return None
+        return findModule(name)
+    findModule = ContextMethod(findModule)
+
+    def __import(wrapped_self, module_name):
+
+        mod = wrapped_self.findModule(module_name)
+        if mod is None:
+            mod = sys.modules.get(module_name)
+            if mod is None:
+                raise ImportError(module_name)
+
+        return mod
+    __import = ContextMethod(__import)
+
+    def resolve(wrapped_self, name):
+
+        name = name.strip()
+
+        if name.endswith('.') or name.endswith('+'):
+            name = name[:-1]
+            repeat = 1
+        else:
+            repeat = 0
+
+        names=name.split('.')
+        last=names[-1]
+        mod='.'.join(names[:-1])
+
+        if not mod:
+            return wrapped_self.__import(name)
+
+        while 1:
+            m = wrapped_self.__import(mod)
+            try:
+                a=getattr(m, last)
+            except AttributeError:
+                if not repeat:
+                    return wrapped_self.__import(name)
+
+            else:
+                if not repeat or (not isinstance(a, ModuleType)):
+                    return a
+            mod += '.' + last
+    resolve = ContextMethod(resolve)


=== Zope3/src/zope/app/services/session.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zope/app/services/session.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,99 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+"""Simplistic session service implemented using cookies.
+
+This is more of a demonstration than a full implementation, but it should
+work.
+"""
+
+# System imports
+import sha, time, string, random, hmac
+
+# Zope3 imports
+from persistence import Persistent
+from persistence.dict import PersistentDict
+from zope.server.http.http_date import build_http_date
+
+# Sibling imports
+from zope.app.interfaces.services.session import ISessionService, IConfigureSessionService
+
+
+cookieSafeTrans = string.maketrans("+/", "-.")
+
+def digestEncode(s):
+    """Encode SHA digest for cookie."""
+    return s.encode("base64")[:-2].translate(cookieSafeTrans)
+
+
+class CookieSessionService(Persistent):
+    """Session service implemented using cookies."""
+
+    __implements__ = Persistent.__implements__, ISessionService, IConfigureSessionService
+
+    def __init__(self):
+        self.dataManagers = PersistentDict()
+        self.namespace = "zope3-cs-%x" % (int(time.time()) - 1000000000)
+        self.secret = "%.20f" % random.random()
+
+    def generateUniqueId(self):
+        """Generate a new, random, unique id."""
+        data = "%.20f%.20f%.20f" % (random.random(), time.time(), time.clock())
+        digest = sha.sha(data).digest()
+        s = digestEncode(digest)
+        # we store a HMAC of the random value together with it, which makes
+        # our session ids unforgeable.
+        mac = hmac.new(s, self.secret, digestmod=sha).digest()
+        return s + digestEncode(mac)
+
+    def getRequestId(self, request):
+        """Return the sessionId encoded in request or None if it's non-existent."""
+        sid = request.cookies.get(self.namespace)
+        if sid is None or len(sid) != 54:
+            return None
+        s, mac = sid[:27], sid[27:]
+        if digestEncode(hmac.new(s, self.secret, digestmod=sha).digest()) != mac:
+            return None
+        else:
+            return sid
+
+    def setRequestId(self, request, id):
+        """Set cookie with id on request."""
+        request.response.setCookie(self.namespace, id, expires=build_http_date(time.time() + 1800))
+
+
+    #####################################
+    # Implementation of ISessionService #
+
+    def getSessionId(self, request):
+        sid = self.getRequestId(request)
+        if sid is None:
+            sid = self.generateUniqueId()
+        self.setRequestId(request, sid)
+        return sid
+
+    def invalidate(self, sessionId):
+        for d in self.dataManagers.values():
+            d.deleteData(sessionId)
+
+    def getDataManager(self, name):
+        return self.dataManagers[name]
+
+    def registerDataManager(self, name, dataManager):
+        if self.dataManagers.has_key(name):
+            raise ValueError, "DataManager already registered with name %r" % name
+        self.dataManagers[name] = dataManager
+
+    def unregisterDataManager(self, name):
+        del self.dataManagers[name]


=== Zope3/src/zope/app/services/session.txt 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zope/app/services/session.txt	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,22 @@
+Sessions allow us to fake state over a stateless protocol - HTTP. We do this
+by having a unique identifier stored across multiple HTTP requests, be it
+a cookie or some id mangled into the URL.
+
+ISessionService provides this unique id. ISessionDataManagers attach data
+objects of some sort to a specific session id. This data object may be
+a Persistent ZODB object, or an object that stores data in a RDBMS using
+the session id as the key.
+
+ISessionDataManagers are registered with ISessionServices using a name,
+which ought to be unique for each application. Thus you can have multiple
+data objects registered with a single session.
+
+ISessionServices may choose to expire sessions. In this case they
+should notify all ISessionDataManagers registered with them and
+invalidate the attached data objects. Likewise ISessionDataManagers
+probably want to expire data.
+
+
+--
+$Id$
+


=== Zope3/src/zope/app/services/test_cookiesessionservice.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:52 2002
+++ Zope3/src/zope/app/services/test_cookiesessionservice.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,126 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+
+from unittest import TestCase, TestLoader, TextTestRunner
+from zope.app.services.tests.placefulsetup \
+    import PlacefulSetup
+from zope.component import getServiceManager, getService
+from zope.server.http.http_date import parse_http_date
+
+from zope.app.interfaces.services.session import \
+     ISessionService, ISessionDataManager
+from zope.app.services.session import \
+     CookieSessionService
+
+import time
+
+
+class DummyDataManager:
+
+    __implements__ = ISessionDataManager
+
+    def __init__(self):
+        self.data = {}
+
+    def getDataObject(self, sid):
+        return self.data.setdefault(sid, {})
+
+    def deleteData(self, sid):
+        del self.data[sid]
+
+
+class FakeRequest:
+
+    def __init__(self):
+        self.sets = 0
+        self.cookies = {}
+        self.response = self
+
+    def setCookie(self, k, v, **kw):
+        self.sets += 1
+        self.cookies[k] = v
+        if not abs(parse_http_date(kw["expires"]) - int(time.time()) - 1800) < 3:
+            raise AssertionError
+
+
+class SessionServiceTestCaseMixin(PlacefulSetup):
+
+    serviceFactory = None
+
+    def setUp(self):
+        PlacefulSetup.setUp(self)
+        self.buildFolders()
+        root_sm = getServiceManager(None)
+        svc = self.serviceFactory()
+        root_sm.defineService("SessionService", ISessionService)
+        root_sm.provideService("SessionService", svc)
+        self.svc = getService(self.rootFolder, "SessionService")
+
+    def testRegister(self):
+        d = DummyDataManager()
+        d2 = DummyDataManager()
+        self.svc.registerDataManager("foo", d)
+        self.assertRaises(ValueError, self.svc.registerDataManager, "foo", d2)
+        self.assertEquals(self.svc.getDataManager("foo"), d)
+        self.svc.unregisterDataManager("foo")
+        self.assertRaises(KeyError, self.svc.getDataManager, "foo")
+        self.svc.registerDataManager("foo", d2)
+
+    def testCookie(self):
+        req = FakeRequest()
+        sid = self.svc.generateUniqueId()
+        self.svc.setRequestId(req, sid)
+        self.assertEquals(self.svc.getRequestId(req), sid)
+
+    def testGetSession(self):
+        req = FakeRequest()
+        sid = self.svc.getSessionId(req)
+        self.assertEquals(req.sets, 1)
+        self.assertEquals(self.svc.getRequestId(req), sid)
+        self.assertEquals(self.svc.getSessionId(req), sid)
+        # make sure cookie was also set during 2nd getSessionId
+        self.assertEquals(req.sets, 2)
+
+    def testLookupAndInvalidate(self):
+        dm = DummyDataManager()
+        svc = self.svc
+        svc.registerDataManager("dm", dm)
+        req = FakeRequest()
+        from zope.app.services.session import getSessionDataObject
+        d = getSessionDataObject(self.rootFolder, req, "dm")
+        d["a"] = "b"
+        self.assert_(d is dm.getDataObject(svc.getSessionId(req)))
+        self.assertEquals("b", dm.getDataObject(svc.getSessionId(req))["a"])
+        svc.invalidate(svc.getSessionId(req))
+        d2 = getSessionDataObject(self.rootFolder, req, "dm")
+        self.assertEquals(d2, {})
+
+    def testForgingCookies(self):
+        for fakeValue in ["dsada", "2" * 54]:
+            req = FakeRequest()
+            self.svc.setRequestId(req, fakeValue)
+            self.assertEquals(self.svc.getRequestId(req), None)
+
+
+class CookieServiceTestCase(SessionServiceTestCaseMixin, TestCase):
+
+    serviceFactory = CookieSessionService
+
+
+def test_suite():
+    loader=TestLoader()
+    return loader.loadTestsFromTestCase(CookieServiceTestCase)
+
+if __name__=='__main__':
+    TextTestRunner().run(test_suite())


=== Zope3/src/zope/app/services/view.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:53 2002
+++ Zope3/src/zope/app/services/view.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,280 @@
+##############################################################################
+#
+# Copyright (c) 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.
+#
+##############################################################################
+"""View Service
+
+
+$Id$
+"""
+__metaclass__ = type
+
+from persistence import Persistent
+from persistence.dict import PersistentDict
+from zope.component.interfaces import IViewService
+from zope.component.exceptions import ComponentLookupError
+from zope.component import getServiceManager
+from zope.app.interfaces.services.configuration import IConfigurable
+from zope.app.services.configuration import ConfigurationRegistry
+from zope.app.services.configuration import SimpleConfiguration
+from zope.proxy.context import ContextWrapper
+from zope.proxy.context import ContextMethod
+from zope.app.services.configuration import ConfigurationStatusProperty
+from zope.app.component.nextservice import getNextService
+from zope.component import getSkin
+
+from zope.proxy.introspection import removeAllProxies
+from zope.app.traversing import getPhysicalRoot, traverse
+from zope.exceptions import NotFoundError
+
+from zope.app.interfaces.services.interfaces import IViewConfiguration, IPageConfiguration
+from zope.app.services.adapter import PersistentAdapterRegistry
+
+class ViewService(Persistent):
+
+    __implements__ = IViewService, IConfigurable
+
+    def __init__(self):
+        self._layers = PersistentDict()
+
+    def queryConfigurationsFor(self, configuration, default=None):
+        "See IConfigurable"
+        return self.queryConfigurations(
+            configuration.viewName, configuration.layer,
+            configuration.forInterface, configuration.presentationType,
+            default)
+
+    queryConfigurationsFor = ContextMethod(queryConfigurationsFor)
+
+    def queryConfigurations(self, name, layer,
+                            forInterface, presentationType, default=None):
+
+        names = self._layers.get(layer)
+        if names is None:
+            return default
+
+        adapter_registry = names.get(name)
+        if adapter_registry is None:
+            return default
+
+        registry = adapter_registry.getRegistered(
+            forInterface, presentationType)
+
+        if registry is None:
+            return default
+
+        return ContextWrapper(registry, self)
+
+    queryConfigurations = ContextMethod(queryConfigurations)
+
+    def createConfigurationsFor(self, configuration):
+        "See IConfigurable"
+        return self.createConfigurations(
+            configuration.viewName, configuration.layer,
+            configuration.forInterface, configuration.presentationType)
+
+    createConfigurationsFor = ContextMethod(createConfigurationsFor)
+
+    def createConfigurations(self,
+                             viewName, layer, forInterface, presentationType):
+
+        names = self._layers.get(layer)
+        if names is None:
+            names = PersistentDict()
+            self._layers[layer] = names
+
+        adapter_registry = names.get(viewName)
+        if adapter_registry is None:
+            adapter_registry = PersistentAdapterRegistry()
+            names[viewName] = adapter_registry
+
+        registry = adapter_registry.getRegistered(
+            forInterface, presentationType)
+
+        if registry is None:
+            registry = ConfigurationRegistry()
+            adapter_registry.register(forInterface, presentationType, registry)
+
+        return ContextWrapper(registry, self)
+
+    createConfigurations = ContextMethod(createConfigurations)
+
+    def getView(self, object, name, request):
+        view = self.queryView(object, name, request)
+        if view is None:
+            raise ComponentLookupError(object, name)
+        return view
+
+    getView = ContextMethod(getView)
+
+    def queryView(self, object, name, request, default=None):
+
+        type = request.getPresentationType()
+        skin = request.getPresentationSkin()
+
+        for layername in getSkin(object, skin, type):
+            layer = self._layers.get(layername)
+            if not layer:
+                continue
+
+            reg = layer.get(name, None)
+            if reg is None:
+                continue
+
+            registry = reg.getForObject(
+                object, type,
+                filter = lambda registry:
+                         ContextWrapper(registry, self).active(),
+                )
+
+            if registry is None:
+                continue
+
+            registry = ContextWrapper(registry, self)
+            view = registry.active().getView(object, request)
+            return view
+
+        views = getNextService(self, 'Views')
+
+        return views.queryView(object, name, request, default)
+
+    queryView = ContextMethod(queryView)
+
+    def getDefaultViewName(self, object, request):
+        "See IViewService"
+
+        name = self.queryDefaultViewName(object, request)
+
+        if name is None:
+            raise NotFoundError, \
+                  'No default view name found for object %s' % object
+
+        return name
+
+    getDefaultViewName = ContextMethod(getDefaultViewName)
+
+    def queryDefaultViewName(self, object, request, default=None):
+        "See IViewService"
+
+        # XXX: need to do our own defaults as well.
+        views = getNextService(self, 'Views')
+        return views.queryDefaultViewName(object, request, default)
+
+    queryDefaultViewName = ContextMethod(queryDefaultViewName)
+
+    def getRegisteredMatching(self,
+                              required_interfaces=None,
+                              presentation_type=None,
+                              viewName=None,
+                              layer=None,
+                              ):
+        if layer is None:
+            layers = self._layers.keys()
+        else:
+            layers = (layer, )
+
+        result = []
+
+        for layer in layers:
+            names_dict = self._layers.get(layer)
+            if names_dict is None:
+                continue
+
+            if viewName is None:
+                viewNames = names_dict.keys()
+            else:
+                viewNames = (viewName, )
+
+            for viewName in viewNames:
+                registry = names_dict.get(viewName)
+
+                if registry is None:
+                    continue
+
+                for match in registry.getRegisteredMatching(
+                    required_interfaces,
+                    presentation_type):
+
+                    result.append(match + (layer, viewName))
+
+        return result
+
+class ViewConfiguration(SimpleConfiguration):
+
+    __implements__ = IViewConfiguration
+
+    status = ConfigurationStatusProperty('Views')
+
+    def __init__(self,
+                 forInterface, viewName, presentationType,
+                 factoryName, layer='default'):
+        self.forInterface = forInterface
+        self.presentationType = presentationType
+        self.factoryName = factoryName
+        self.viewName = viewName
+        self.layer = layer
+
+    def getView(self, object, request):
+        sm = getServiceManager(self)
+        factory = sm.resolve(self.factoryName)
+        return factory(object, request)
+
+    getView = ContextMethod(getView)
+
+class PageConfiguration(ViewConfiguration):
+
+    __implements__ = IPageConfiguration
+
+    def __init__(self,
+                 forInterface, viewName, presentationType,
+                 factoryName=None, template=None,
+                 layer='default'):
+        super(PageConfiguration, self).__init__(
+            forInterface, viewName, presentationType,
+            factoryName, layer)
+
+        self.template = template
+
+    def getView(self, object, request):
+        sm = getServiceManager(self)
+
+        if self.factoryName:
+            factory = sm.resolve(self.factoryName)
+        else:
+            factory = DefaultFactory
+
+        view = factory(object, request)
+
+        # This is needed because we need to do an unrestricted traverse
+        root = removeAllProxies(getPhysicalRoot(sm))
+
+        template = traverse(root, self.template)
+
+        return BoundTemplate(template, view)
+
+    getView = ContextMethod(getView)
+
+
+class DefaultFactory:
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+class BoundTemplate:
+
+    def __init__(self, template, view):
+        self.template = template
+        self.view = view
+
+    def __call__(self, *args, **kw):
+        return self.template.render(self.view, *args, **kw)


=== Zope3/src/zope/app/services/viewpackage.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:53 2002
+++ Zope3/src/zope/app/services/viewpackage.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,74 @@
+##############################################################################
+#
+# Copyright (c) 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.
+#
+##############################################################################
+"""View package.
+
+$Id$
+"""
+__metaclass__ = type
+
+from zope.app.container.btree import BTreeContainer
+from zope.app.interfaces.services.interfaces import IZPTTemplate
+from zope.publisher.interfaces.browser import IBrowserPresentation
+from zope.app.traversing import getPhysicalPathString, traverse
+from zope.proxy.context import getItem, getAttr
+from zope.proxy.context import ContextMethod
+from zope.app.interfaces.services.configuration import Active
+from zope.app.services.configurationmanager \
+     import ConfigurationManager
+from zope.app.services.configuration import ConfigurationStatusProperty
+from zope.proxy.introspection import removeAllProxies
+from zope.app.services.view import PageConfiguration
+from zope.app.interfaces.services.service import IViewPackage
+
+class ViewPackage(BTreeContainer):
+
+    __implements__ = IViewPackage
+
+
+    presentationType = IBrowserPresentation
+    layer = "default"
+    description = ''
+    title = ''
+
+    def __init__(self):
+        super(ViewPackage, self).__init__()
+        super(ViewPackage, self).setObject('configure', ConfigurationManager())
+
+    def setObject(self, name, object):
+        if not IZPTTemplate.isImplementedBy(object):
+            raise TypeError("Can only add packages")
+
+        # super() does not work on a context wrapped instance
+        base = removeAllProxies(self)
+
+        name = super(ViewPackage, base).setObject(name, object)
+        template = getItem(self, name)
+        template = getPhysicalPathString(template)
+        config = PageConfiguration(self.forInterface, name,
+                                   self.presentationType,
+                                   self.factoryName, template,
+                                   self.layer)
+        configure = traverse(self, 'configure')
+        id = configure.setObject('', config)
+        config = getItem(configure, id)
+        config.status = Active
+        return name
+
+    setObject = ContextMethod(setObject)
+
+    def activated(self):
+        "See IConfiguration"
+
+    def deactivated(self):
+        "See IConfiguration"


=== Zope3/src/zope/app/services/zpt.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:13:53 2002
+++ Zope3/src/zope/app/services/zpt.py	Wed Dec 25 09:13:19 2002
@@ -0,0 +1,89 @@
+##############################################################################
+#
+# Copyright (c) 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.
+#
+##############################################################################
+"""
+$Id$
+"""
+
+import re
+
+from zope.interface import Interface, Attribute
+import zope.schema
+from persistence import Persistent
+
+from zope.proxy.context import ContextMethod
+from zope.proxy.context import getWrapperContainer
+from zope.security.proxy import ProxyFactory
+
+from zope.app.interfaces.content.file import IFileContent
+from zope.pagetemplate.pagetemplate import PageTemplate
+from zope.app.pagetemplate.engine import AppPT
+from zope.app.interfaces.services.interfaces import IZPTTemplate
+
+class ZPTTemplate(AppPT, PageTemplate, Persistent):
+
+    __implements__ = IZPTTemplate
+
+    contentType = 'text/html'
+
+    source = property(
+        # get
+        lambda self: self.read(),
+        # set
+        lambda self, text: self.pt_edit(text.encode('utf-8'), self.contentType)
+        )
+
+    def pt_getContext(self, view, **_kw):
+        # instance is a View component
+        namespace = super(ZPTTemplate, self).pt_getContext(**_kw)
+        namespace['view'] = view
+        namespace['request'] = view.request
+        namespace['context'] = view.context
+        return namespace
+
+    def render(self, view, *args, **keywords):
+
+        if args:
+            args = ProxyFactory(args)
+        kw = ProxyFactory(keywords)
+
+        namespace = self.pt_getContext(view, args=args, options=kw)
+
+        return self.pt_render(namespace)
+
+# Adapter for ISearchableText
+
+from zope.app.interfaces.index.text.interfaces import ISearchableText
+
+tag = re.compile(r"<[^>]+>")
+class SearchableText:
+
+    __implements__ = ISearchableText
+    __used_for__ = IZPTTemplate
+
+    def __init__(self, page):
+        self.page = page
+
+    def getSearchableText(self):
+        text = self.page.source
+        if isinstance(text, str):
+            text = unicode(self.page.source, 'utf-8')
+        # else:
+        #   text was already Unicode, which happens, but unclear how it
+        #   gets converted to Unicode since the ZPTPage stores UTF-8 as
+        #   an 8-bit string.
+
+        if self.page.contentType.startswith('text/html'):
+            text = tag.sub('', text)
+
+        return [text]