[Zope3-checkins] CVS: Zope3/src/zope/app/publication - __init__.py:1.2 browser.py:1.2 configure.zcml:1.2 http.py:1.2 publicationtraverse.py:1.2 traversers.py:1.2 vfs.py:1.2 xmlrpc.py:1.2 zopepublication.py:1.2

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


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

Added Files:
	__init__.py browser.py configure.zcml http.py 
	publicationtraverse.py traversers.py vfs.py xmlrpc.py 
	zopepublication.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/publication/__init__.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/publication/__init__.py	Wed Dec 25 09:13:08 2002
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.


=== Zope3/src/zope/app/publication/browser.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/publication/browser.py	Wed Dec 25 09:13:08 2002
@@ -0,0 +1,84 @@
+##############################################################################
+#
+# 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.publication.publicationtraverse \
+     import PublicationTraverser as PublicationTraverser_
+from zope.publisher.interfaces.browser import IBrowserPublisher
+from zope.component import queryAdapter
+
+class PublicationTraverser(PublicationTraverser_):
+
+    def traverseRelativeURL(self, request, ob, path):
+
+        ob = self.traversePath(request, ob, path)
+
+        while 1:
+            adapter = queryAdapter(ob, IBrowserPublisher)
+            if adapter is None:
+                return ob
+            ob, path = adapter.browserDefault(request)
+            if not path:
+                return ob
+
+            ob = self.traversePath(request, ob, path)
+
+
+"""
+
+Revision information:
+$Id$
+"""
+
+from zope.app.publication.http import ZopeHTTPPublication
+from zope.publisher.interfaces.browser import IBrowserPublisher
+from zope.component import queryView
+from zope.proxy.context import ContextWrapper
+from zope.proxy.introspection import removeAllProxies
+
+class BrowserPublication(ZopeHTTPPublication):
+    """Web browser publication handling."""
+
+    def getDefaultTraversal(self, request, ob):
+
+        r = ()
+
+        if IBrowserPublisher.isImplementedBy(removeAllProxies(ob)):
+            r = ob.browserDefault(request)
+        else:
+            adapter = queryView(ob, '_traverse', request , None)
+            if adapter is not None:
+                r = adapter.browserDefault(request)
+            else:
+                return (ob, None)
+
+        if r[0] is ob: return r
+
+        wrapped = ContextWrapper(r[0], ob, name=None)
+        return (wrapped, r[1])
+
+# For now, have a factory that returns a singleton
+class PublicationFactory:
+
+    def __init__(self, db):
+        self.__pub = BrowserPublication(db)
+
+    def __call__(self):
+        return self.__pub


=== Zope3/src/zope/app/publication/configure.zcml 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/publication/configure.zcml	Wed Dec 25 09:13:08 2002
@@ -0,0 +1,19 @@
+<zopeConfigure
+   xmlns='http://namespaces.zope.org/zope'
+   xmlns:browser='http://namespaces.zope.org/browser'
+   xmlns:xmlrpc='http://namespaces.zope.org/xmlrpc'
+>
+
+<browser:view name="_traverse" 
+ for="zope.interface.Interface"
+ factory="zope.app.publication.traversers.SimpleComponentTraverser" />
+
+<browser:view name="_traverse" 
+ for="zope.app.interfaces.content.file.IFileContent"
+ factory="zope.app.publication.traversers.FileContentTraverser" />
+
+  <xmlrpc:view name="_traverse" 
+   for="zope.interface.Interface"
+   factory="zope.app.publication.traversers.SimpleComponentTraverser" />
+
+</zopeConfigure>


=== Zope3/src/zope/app/publication/http.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/publication/http.py	Wed Dec 25 09:13:08 2002
@@ -0,0 +1,23 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from zope.app.publication.zopepublication import ZopePublication
+
+class ZopeHTTPPublication(ZopePublication):
+    "HTTP-specific support"
+    # XXX do we need this?


=== Zope3/src/zope/app/publication/publicationtraverse.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/publication/publicationtraverse.py	Wed Dec 25 09:13:08 2002
@@ -0,0 +1,113 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from zope.component import queryView, getService
+from zope.publisher.interfaces import NotFound
+from types import StringTypes
+from zope.proxy.context import ContextWrapper
+
+from zope.app.interfaces.container import IWriteContainer
+from zope.proxy.introspection import removeAllProxies
+from zope.app.traversing.namespaces import namespaceLookup
+from zope.app.traversing.parameterparsing import parameterizedNameParse
+from zope.publisher.interfaces import IPublishTraverse
+
+class DuplicateNamespaces(Exception):
+    """More than one namespace was specified in a request"""
+
+class UnknownNamespace(Exception):
+    """A parameter specified an unknown namespace"""
+
+class PublicationTraverse:
+
+    def traverseName(self, request, ob, name):
+        nm = name # the name to look up the object with
+
+        if name and name[:1] in '@+':
+            # Process URI segment parameters.
+            ns, nm, parms = parameterizedNameParse(name)
+
+            unknown_parms = ()
+            for pname, pval in parms:
+                pset = getattr(self, "_parameterSet%s" % pname, self) # marker
+                if pset is self:
+                    # We don't know about this one
+                    unknown_parms += ((pname, pval),)
+                else:
+                    pset(pname, pval, request)
+
+            if ns:
+                ob2 = namespaceLookup(name, ns, nm, unknown_parms, ob, request)
+                return ob2
+
+            if unknown_parms:
+                nm = "%s;%s" % (
+                    nm,
+                    ';'.join(["%s=%s" % (parm[0], parm[1])
+                              for parm in unknown_parms])
+                    )
+
+            if not nm:
+                # Just set params, so skip
+                return ob
+
+        if nm == '.':
+            return ob
+
+        if IPublishTraverse.isImplementedBy(removeAllProxies(ob)):
+            ob2 = ob.publishTraverse(request, nm)
+        else:
+            adapter = queryView(ob, '_traverse', request, self) # marker
+            if adapter is not self:
+                ob2 = adapter.publishTraverse(request, nm)
+            else:
+                raise NotFound(ob, name, request)
+
+        return ContextWrapper(ob2, ob, name=name)
+
+class PublicationTraverser(PublicationTraverse):
+
+    def traversePath(self, request, ob, path):
+
+        if isinstance(path, StringTypes):
+            path = path.split('/')
+            if len(path) > 1 and not path[-1]:
+                # Remove trailing slash
+                path.pop()
+        else:
+            path = list(path)
+
+        # Remove dingle dots
+        path = [x for x in path if x != '.']
+
+        path.reverse()
+
+        # Remove double dots
+        while '..' in path:
+            l = path.index('..')
+            if l < 0 or l+2 > len(path):
+                break
+            del path[l:l+2]
+
+        pop = path.pop
+
+        while path:
+            name = pop()
+            ob = self.traverseName(request, ob, name)
+
+        return ob


=== Zope3/src/zope/app/publication/traversers.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/publication/traversers.py	Wed Dec 25 09:13:08 2002
@@ -0,0 +1,110 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"$Id"
+
+__metaclass__ = type
+
+
+from zope.publisher.interfaces import Unauthorized, NotFound, DebugError
+from zope.publisher.interfaces.browser import IBrowserPublisher
+from zope.publisher.interfaces.xmlrpc import IXMLRPCPublisher
+from zope.component \
+     import queryView, getView, getDefaultViewName
+from zope.component.exceptions import ComponentLookupError
+
+class SimpleComponentTraverser:
+    """Browser traverser for simple components that can only traverse to views
+    """
+    __implements__ = IBrowserPublisher, IXMLRPCPublisher
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def browserDefault(self, request):
+        ob = self.context
+
+        view_name = getDefaultViewName(ob, request)
+
+        return ob, (view_name,)
+
+    def publishTraverse(self, request, name):
+        ob = self.context
+        from zope.component.view import viewService
+        view = queryView(ob, name, request)
+        if view is None:
+            raise NotFound(ob, name)
+        return view
+
+class FileContentTraverser(SimpleComponentTraverser):
+    """Browser traverser for file content.
+
+    The default view for file content has effective URLs that don't end in
+    /.  In particular, if the content inclused HTML, relative links in
+    the HTML are relative to the container the content is in.
+
+    """
+
+    def browserDefault(self, request):
+        ob = self.context
+
+        view_name = getDefaultViewName(ob, request)
+        view = self.publishTraverse(request, view_name)
+        if hasattr(view, 'browserDefault'):
+            view, path = view.browserDefault(request)
+            if len(path) == 1:
+                view = view.publishTraverse(request, path[0])
+                path = ()
+        else:
+            path = ()
+
+        return view, path
+
+class TestTraverser:
+    "Bobo-style traverser, mostly useful for testing"
+
+    __implements__ = IBrowserPublisher
+
+    def __init__(self, context, request):
+        self.context = context
+
+    def browserDefault(self, request):
+        ob = self.context
+
+        if hasattr(ob, '__implements__'):
+
+            view_name = getDefaultViewName(ob, request)
+
+            return ob, (("@@%s" % view_name),)
+
+        return ob, ()
+
+    def publishTraverse(self, request, name):
+        ob = self.context
+        if name.startswith('@@'):
+            return getView(ob, name[6:], request)
+
+        if name.startswith('_'):
+            raise Unauthorized("Name %s begins with an underscore" % `name`)
+
+        subob = getattr(ob, name, self) # self is marker here
+        if subob is self:
+            # no attribute
+            try:
+                subob = ob[name]
+            except (KeyError, IndexError,
+                    TypeError, AttributeError):
+                raise NotFound(ob, name, request)
+
+        return subob


=== Zope3/src/zope/app/publication/vfs.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/publication/vfs.py	Wed Dec 25 09:13:08 2002
@@ -0,0 +1,40 @@
+##############################################################################
+#
+# 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
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from zope.app.publication.zopepublication import ZopePublication
+
+from zope.component import queryView
+from zope.publisher.interfaces import NotFound
+from zope.publisher.publish import mapply
+
+
+class VFSPublication(ZopePublication):
+    """The Publication will do all the work for the VFS"""
+
+
+    def callObject(self, request, ob):
+
+        view = queryView(ob, 'vfs', request, self)
+        #view = ob
+
+        if view is not self:
+            method = getattr(view, request.method)
+        else:
+            raise NotFound(ob, 'vfs', request)
+
+        return mapply(method, request.getPositionalArguments(), request)


=== Zope3/src/zope/app/publication/xmlrpc.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/publication/xmlrpc.py	Wed Dec 25 09:13:08 2002
@@ -0,0 +1,45 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+
+from zope.proxy.introspection import removeAllProxies
+from zope.app.publication.http import ZopeHTTPPublication
+
+class XMLRPCPublication(ZopeHTTPPublication):
+    """XML-RPC publication handling.
+
+       There is nothing special here right now.
+    """
+
+    def traverseName(self, request, ob, name):
+
+        naked_ob = removeAllProxies(ob)
+        if hasattr(ob, name):
+            return getattr(ob, name)
+        else:
+            return super(XMLRPCPublication, self).traverseName(request,
+                                                               ob, name)
+
+
+# For now, have a factory that returns a singleton
+class XMLRPCPublicationFactory:
+
+    def __init__(self, db):
+        self.__pub = XMLRPCPublication(db)
+
+    def __call__(self):
+        return self.__pub


=== Zope3/src/zope/app/publication/zopepublication.py 1.1 => 1.2 ===
--- /dev/null	Wed Dec 25 09:14:09 2002
+++ Zope3/src/zope/app/publication/zopepublication.py	Wed Dec 25 09:13:08 2002
@@ -0,0 +1,253 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+import sys
+import logging
+
+from zope.component import getService
+from zope.component.exceptions import ComponentLookupError
+from zodb.interfaces import ConflictError
+
+from zope.publisher.base import DefaultPublication
+from zope.publisher.publish import mapply
+from zope.publisher.interfaces import Retry
+
+from zope.security.securitymanagement import getSecurityManager
+from zope.security.securitymanagement import newSecurityManager
+from zope.security.checker import ProxyFactory
+
+from zope.proxy.introspection import removeAllProxies
+
+from zope.app.interfaces.services.service \
+     import IServiceManagerContainer
+
+from zope.exceptions import Unauthorized
+
+from zope.app.applicationcontrol.applicationcontrol \
+     import applicationControllerRoot
+
+from zope.app.security.registries.principalregistry \
+     import principalRegistry as prin_reg
+
+from zope.app.interfaces.security \
+     import IUnauthenticatedPrincipal
+
+from zope.app.publication.publicationtraverse import PublicationTraverse
+
+from zope.proxy.context import ContextWrapper
+
+# XXX Should this be imported here?
+from transaction import get_transaction
+
+class Cleanup:
+    def __init__(self, f):
+        self.__del__ = f
+
+
+class ZopePublication(object, PublicationTraverse, DefaultPublication):
+    """Base Zope publication specification."""
+
+    version_cookie = 'Zope-Version'
+    root_name = 'Application'
+
+    def __init__(self, db):
+        # db is a ZODB.DB.DB object.
+        self.db = db
+
+    def beforeTraversal(self, request):
+
+        # Try to authenticate against the default global registry.
+        p = prin_reg.authenticate(request)
+        if p is None:
+            p = prin_reg.unauthenticatedPrincipal()
+            if p is None:
+                raise Unauthorized # If there's no default principal
+
+        newSecurityManager(p.getId())
+        request.user = p
+        get_transaction().begin()
+
+    def _maybePlacefullyAuthenticate(self, request, ob):
+        if not IUnauthenticatedPrincipal.isImplementedBy(request.user):
+            # We've already got an authenticated user. There's nothing to do.
+            # Note that beforeTraversal guarentees that user is not None.
+            return
+
+        if not IServiceManagerContainer.isImplementedBy(ob):
+            # We won't find an authentication service here, so give up.
+            return
+
+        sm = removeAllProxies(ob).queryServiceManager()
+        if sm is None:
+            # No service manager here, and thus no auth service
+            return
+
+        sm = ContextWrapper(sm, ob, name="++etc++Services")
+
+        auth_service = sm.get('Authentication')
+        if auth_service is None:
+            # No auth service here
+            return
+
+        # Try to authenticate against the auth service
+        principal = auth_service.authenticate(request)
+        if principal is None:
+            principal = auth_service.unauthenticatedPrincipal()
+            if principal is None:
+                # nothing to do here
+                return
+
+        newSecurityManager(principal.getId())
+        request.user = principal
+
+
+    def callTraversalHooks(self, request, ob):
+        # Call __before_publishing_traverse__ hooks
+
+        # This is also a handy place to try and authenticate.
+        self._maybePlacefullyAuthenticate(request, ob)
+
+    def afterTraversal(self, request, ob):
+        #recordMetaData(object, request)
+        self._maybePlacefullyAuthenticate(request, ob)
+
+
+    def openedConnection(self, conn):
+        # Hook for auto-refresh
+        pass
+
+    def getApplication(self, request):
+
+        # If the first name is '++etc++ApplicationControl', then we should
+        # get it rather than look in the database!
+        stack = request.getTraversalStack()
+
+        if '++etc++ApplicationController' in stack:
+            return applicationControllerRoot
+
+        # Open the database.
+        version = request.get(self.version_cookie, '')
+        conn = self.db.open(version)
+
+        cleanup = Cleanup(conn.close)
+        request.hold(cleanup)  # Close the connection on request.close()
+
+        self.openedConnection(conn)
+##        conn.setDebugInfo(getattr(request, 'environ', None), request.other)
+
+        root = conn.root()
+        app = root.get(self.root_name, None)
+
+        if app is None:
+            raise SystemError, "Zope Application Not Found"
+
+        return ProxyFactory(app)
+
+    def getDefaultTraversal(self, request, ob):
+        return ob, None
+
+    def callObject(self, request, ob):
+        return mapply(ob, request.getPositionalArguments(), request)
+
+    def afterCall(self, request):
+        get_transaction().commit()
+
+    def handleException(self, object, request, exc_info, retry_allowed=1):
+        try:
+            # Abort the transaction.
+            get_transaction().abort()
+
+            try:
+                errService = getService(object,'ErrorReportingService')
+            except ComponentLookupError:
+                pass
+            else:
+                try:
+                    errService.raising(exc_info, request)
+                # It is important that an error in errService.raising
+                # does not propagate outside of here. Otherwise, nothing
+                # meaningful will be returned to the user.
+                #
+                # The error reporting service should not be doing database
+                # stuff, so we shouldn't get a conflict error.
+                # Even if we do, it is more important that we log this
+                # error, and proceed with the normal course of events.
+                # We should probably (somehow!) append to the standard
+                # error handling that this error occurred while using
+                # the ErrorReportingService, and that it will be in
+                # the zope log.
+                except:
+                    logging.getLogger('SiteError').exception(
+                        'Error while reporting an error to the '
+                        'ErrorReportingService')
+
+            # Delegate Unauthorized errors to the authentication service
+            # XXX Is this the right way to handle Unauthorized?  We need
+            # to understand this better.
+            if isinstance(exc_info[1], Unauthorized):
+                sm = getSecurityManager()
+                id = sm.getPrincipal()
+                prin_reg.unauthorized(id, request) # May issue challenge
+                request.response.handleException(exc_info)
+                return
+
+            # XXX This is wrong. Should use getRequstView:
+            #
+            #
+            # # Look for a component to handle the exception.
+            # traversed = request.traversed
+            # if traversed:
+            #     context = traversed[-1]
+            #     #handler = getExceptionHandler(context, t, IBrowserPublisher)
+            #     handler = None  # no getExceptionHandler() exists yet.
+            #     if handler is not None:
+            #         handler(request, exc_info)
+            #         return
+
+            # Convert ConflictErrors to Retry exceptions.
+            if retry_allowed and isinstance(exc_info[1], ConflictError):
+                logger.getLogger('ZopePublication').warn(
+                    'Competing writes/reads at %s',
+                    request.get('PATH_INFO', '???'),
+                    exc_info=True)
+                raise Retry
+
+            # Let the response handle it as best it can.
+            # XXX Is this what we want in the long term?
+            response = request.response
+            response.handleException(exc_info)
+            return
+        finally:
+            # Avoid leaking
+            exc_info = 0
+
+
+    def _parameterSetskin(self, pname, pval, request):
+        request.setViewSkin(pval)
+
+class DebugPublication(object):
+
+    class call_wrapper:
+
+        def __init__(self, ob):
+            self.__ob = ob
+
+        def __getattr__(self, name):
+            return getattr(self.__ob, name)
+
+        def __call__(self, *args, **kw):
+            self.__ob(*args, **kw)
+
+    def callObject(self, request, ob):
+        return mapply(self.call_wrapper(ob),
+                      request.getPositionalArguments(), request)