[Zope3-checkins] CVS: Zope3/src/zope/app/publication - zopepublication.py:1.11

Steve Alexander steve@cat-box.net
Wed, 5 Feb 2003 06:42:02 -0500


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

Modified Files:
	zopepublication.py 
Log Message:
You can now define views on exceptions, so that when an error reaches
the publisher, if there's a view on the exception with the default view
name, that view is presented.
If the exception is adaptable to IExceptionSideEffects, then the adapter
is called in a new transaction so that persistent side-effects can be
effected.


=== Zope3/src/zope/app/publication/zopepublication.py 1.10 => 1.11 ===
--- Zope3/src/zope/app/publication/zopepublication.py:1.10	Mon Feb  3 12:20:24 2003
+++ Zope3/src/zope/app/publication/zopepublication.py	Wed Feb  5 06:41:29 2003
@@ -14,12 +14,13 @@
 import sys
 import logging
 
-from zope.component import getService
+from zope.component import getService, getView, getDefaultViewName
+from zope.component import queryService, getAdapter
 from zope.component.exceptions import ComponentLookupError
 from zodb.interfaces import ConflictError
 
 from zope.publisher.publish import mapply
-from zope.publisher.interfaces import Retry
+from zope.publisher.interfaces import Retry, IExceptionSideEffects
 from zope.publisher.interfaces.http import IHTTPRequest
 
 from zope.security.management import getSecurityManager, newSecurityManager
@@ -70,7 +71,8 @@
                 raise Unauthorized # If there's no default principal
 
         newSecurityManager(p.getId())
-        request.user = p
+        # XXX add a test to check that request.user is context wrapped
+        request.user = ContextWrapper(p, prin_reg)
         get_transaction().begin()
 
     def _maybePlacefullyAuthenticate(self, request, ob):
@@ -104,7 +106,8 @@
                 return
 
         newSecurityManager(principal.getId())
-        request.user = principal
+        # XXX add test that request.user is context-wrapped
+        request.user = ContextWrapper(principal, auth_service)
 
 
     def callTraversalHooks(self, request, ob):
@@ -160,16 +163,49 @@
         txn.setUser(request.user.getId())
         get_transaction().commit()
 
-    def handleException(self, object, request, exc_info, retry_allowed=1):
-        # Abort the transaction.
-        get_transaction().abort()
+    def handleException(self, object, request, exc_info, retry_allowed=True):
+        # Convert ConflictErrors to Retry exceptions.
+        if retry_allowed and isinstance(exc_info[1], ConflictError):
+            get_transaction().abort()
+            tryToLogWarning('ZopePublication',
+                'Competing writes/reads at %s' % 
+                request.get('PATH_INFO', '???'),
+                exc_info=True)
+            raise Retry
+        # Are there any reasons why we'd want to let application-level error
+        # handling determine whether a retry is allowed or not?
+        # Assume not for now.
 
+        response = request.response
+        exception = None
         try:
-            errService = getService(object, 'ErrorReportingService')
-        except ComponentLookupError:
-            pass
-        else:
+            # Set the request body, and abort the current transaction.
             try:
+                exception = ContextWrapper(exc_info[1], object)
+                name = getDefaultViewName(exception, request)
+                view = getView(exception, name, request)
+                response.setBody(self.callObject(request, view))
+            except ComponentLookupError:
+                # No view available for this exception, so let the response
+                # handle it.
+                response.handleException(exc_info)
+            except:
+                # Problem getting a view for this exception. Log an error.
+                tryToLogException('Exception while getting view on exception')
+                # So, let the response handle it.
+                response.handleException(exc_info)
+        finally:
+            # Definitely abort the transaction that raised the exception.
+            get_transaction().abort()
+
+        # New transaction for side-effects
+        beginErrorHandlingTransaction(request)
+        transaction_ok = False
+
+        # Record the error with the ErrorReportingService
+        try:
+            errService = queryService(object, 'ErrorReportingService')
+            if errService is not None:
                 errService.raising(exc_info, request)
             # It is important that an error in errService.raising
             # does not propagate outside of here. Otherwise, nothing
@@ -183,46 +219,26 @@
             # 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
+        except:
+            tryToLogException(
+                'Error while reporting an error to the '
+                'ErrorReportingService')
+            get_transaction().abort()
+            beginErrorHandlingTransaction(request)
 
-        # Convert ConflictErrors to Retry exceptions.
-        if retry_allowed and isinstance(exc_info[1], ConflictError):
-            #XXX this code path needs a unit test
-            logging.getLogger('ZopePublication').warn(
-                'Competing writes/reads at %s',
-                request.get('PATH_INFO', '???'),
-                exc_info=True)
-            raise Retry
+        # See if there's an IExceptionSideEffects adapter for the exception
+        try:
+            adapter = getAdapter(exception, IExceptionSideEffects)
+            # view_presented is None if no view was presented, or the name
+            # of the view, if it was.
+            # Although request is passed in here, it should be considered
+            # read-only.
+            adapter(object, request, exc_info)
+            get_transaction().commit()
+        except:
+            get_transaction().abort()
 
-        # Let the response handle it as best it can.
-        # XXX Is this what we want in the long term?
-        request.response.handleException(exc_info)
         return
 
     def _parameterSetskin(self, pname, pval, request):
@@ -244,3 +260,40 @@
     def callObject(self, request, ob):
         return mapply(self.call_wrapper(ob),
                       request.getPositionalArguments(), request)
+
+def tryToLogException(arg1, arg2=None):
+    if arg2 is None:
+        subsystem = 'SiteError'
+        message = arg1
+    else:
+        subsystem = arg1
+        message = arg2
+    try:
+        logging.getLogger(subsystem).exception(message)
+    # Bare except, because we want to swallow any exception raised while
+    # logging an exception.
+    except:
+        pass
+
+def tryToLogWarning(arg1, arg2=None, exc_info=False):
+    if arg2 is None:
+        subsystem = 'SiteError'
+        message = arg1
+    else:
+        subsystem = arg1
+        message = arg2
+    try:
+        logging.getLogger(subsystem).warn(message, exc_info=exc_info)
+    # Bare except, because we want to swallow any exception raised while
+    # logging a warning.
+    except:
+        pass
+
+def beginErrorHandlingTransaction(request):
+    get_transaction().begin()
+    if IHTTPRequest.isImplementedBy(request):
+        pathnote = '%s ' % request["PATH_INFO"]
+    else:
+        pathnote = ''
+    get_transaction().note(
+        '%s(application error handling)' % pathnote)