[Checkins] SVN: Zope/trunk/ - LP #253362: Moved Zope2-specific moudle, ZODB/Mount.py, to

Tres Seaver tseaver at palladion.com
Fri Oct 3 23:30:23 EDT 2008


Log message for revision 91724:
  - LP #253362:  Moved Zope2-specific moudle, ZODB/Mount.py, to
    Products/TemporaryFolder/mount.py (its only client is
    Products/TemporaryFolder/TemporaryFolder.py).
  
  - Moved exception 'MountedStorageError' from 'ZODB.POSExceptions'
    to 'Products.TemporaryFolder.mount' (now its only client).
  
  - Removed spurious import-time dependencies from
    Products/ZODBMountPoint/MountedObject.py.
  

Changed:
  U   Zope/trunk/doc/CHANGES.txt
  U   Zope/trunk/lib/python/Products/TemporaryFolder/TemporaryFolder.py
  A   Zope/trunk/lib/python/Products/TemporaryFolder/mount.py
  U   Zope/trunk/lib/python/Products/ZODBMountPoint/MountedObject.py

-=-
Modified: Zope/trunk/doc/CHANGES.txt
===================================================================
--- Zope/trunk/doc/CHANGES.txt	2008-10-04 02:50:28 UTC (rev 91723)
+++ Zope/trunk/doc/CHANGES.txt	2008-10-04 03:30:21 UTC (rev 91724)
@@ -5,10 +5,20 @@
   file HISTORY.txt.
 
 
-  Trunk  (unreleased)
+  Trunk (unreleased)
 
     Restructuring
 
+      - Moved exception MountedStorageError from ZODB.POSExceptions
+        to Products.TemporaryFolder.mount (now its only client).
+
+      - LP #253362:  Moved Zope2-specific module, ZODB/Mount.py, to
+        Products/TemporaryFolder/mount.py (its only client is
+        Products/TemporaryFolder/TemporaryFolder.py).
+
+      - Removed spurious import-time dependencies from
+        Products/ZODBMountPoint/MountedObject.py.
+
       - Removed Examples.zexp from the skeleton. The TTW shopping cart isn't
         any good example of Zope usage anymore.
 

Modified: Zope/trunk/lib/python/Products/TemporaryFolder/TemporaryFolder.py
===================================================================
--- Zope/trunk/lib/python/Products/TemporaryFolder/TemporaryFolder.py	2008-10-04 02:50:28 UTC (rev 91723)
+++ Zope/trunk/lib/python/Products/TemporaryFolder/TemporaryFolder.py	2008-10-04 03:30:21 UTC (rev 91724)
@@ -10,8 +10,7 @@
 # FOR A PARTICULAR PURPOSE
 #
 ##############################################################################
-"""
-Mounted database support
+""" Mounted database support
 
 A MountedTemporaryFolder is an object that is a mount point.  It mounts a
 TemporaryStorage-backed database and masquerades as its root object.
@@ -20,24 +19,21 @@
 lives in another ZODB.
 
 To understand this fully, you'll need to read the source of
-ZODB.Mount.MountPoint.
+Products.TemporaryFolder.mount.MountPoint.
 
 $Id$
 """
 __version__='$Revision: 1.12 $'[11:-2]
 
-import os, os.path
-
-import Globals
-from Globals import HTMLFile
-from ZODB.Mount import MountPoint
+from App.special_dtml import DTMLFile
+from App.special_dtml import HTMLFile
 from OFS.Folder import Folder
 from OFS.SimpleItem import Item
-
-from ZODB.DB import DB
 from tempstorage.TemporaryStorage import TemporaryStorage
-from LowConflictConnection import LowConflictConnection
+from ZODB.DB import DB
 
+from Products.TemporaryFolder.mount import MountPoint
+
 ADD_TEMPORARY_FOLDER_PERM="Add Temporary Folder"
 
 
@@ -48,14 +44,15 @@
     if REQUEST is not None:
         return self.manage_main(self, REQUEST, update_menu=1)
 
-
 constructTemporaryFolderForm=HTMLFile('dtml/addTemporaryFolder', globals())
 
+
 class SimpleTemporaryContainer(Folder):
     # dbtab-style container class
     meta_type = 'Temporary Folder'
     icon = 'misc_/TemporaryFolder/tempfolder.gif'
 
+
 class MountedTemporaryFolder(MountPoint, Item):
     """
     A mounted RAM database with a basic interface for displaying the
@@ -73,7 +70,7 @@
         self.title = title
         MountPoint.__init__(self, path='/') # Eep
 
-    manage_traceback = Globals.DTMLFile('dtml/mountfail', globals())
+    manage_traceback = DTMLFile('dtml/mountfail', globals())
 
     def _createDB(self, db=None): # huh?  db=db was original
         """ Create a mounted RAM database """

Added: Zope/trunk/lib/python/Products/TemporaryFolder/mount.py
===================================================================
--- Zope/trunk/lib/python/Products/TemporaryFolder/mount.py	                        (rev 0)
+++ Zope/trunk/lib/python/Products/TemporaryFolder/mount.py	2008-10-04 03:30:21 UTC (rev 91724)
@@ -0,0 +1,294 @@
+##############################################################################
+#
+# 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.1 (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
+#
+##############################################################################
+"""Mounted database support
+
+$Id: Mount.py 82361 2007-12-19 18:44:17Z jim $"""
+
+import time
+import thread
+import logging
+import persistent
+
+from Acquisition import Implicit
+from Acquisition import ImplicitAcquisitionWrapper
+from Acquisition import aq_base
+from ZODB.POSException import StorageError
+
+class MountedStorageError(StorageError):
+    """Unable to access mounted storage."""
+
+logger = logging.getLogger('ZODB.Mount')
+
+# dbs is a holder for all DB objects, needed to overcome
+# threading issues.  It maps connection params to a DB object
+# and a mapping of mount points.
+dbs = {}
+
+# dblock is locked every time dbs is accessed.
+dblock = thread.allocate_lock()
+
+
+def parentClassFactory(jar, module, name):
+    # Use the class factory from the parent database.
+    parent_conn = getattr(jar, '_mount_parent_jar', None)
+    parent_db = getattr(parent_conn, '_db', None)
+    if parent_db is None:
+        _globals = {}
+        _silly = ('__doc__',)
+        return getattr(__import__(
+            module, _globals, _globals, _silly), name)
+    else:
+        return parent_db.classFactory(parent_conn, module, name)
+
+
+class MountPoint(persistent.Persistent, Implicit):
+    '''The base class for a Zope object which, when traversed,
+    accesses a different database.
+    '''
+
+    # Default values for non-persistent variables.
+    _v_db = None
+    _v_data = None
+    _v_connect_error = None
+
+    def __init__(self, path, params=None, classDefsFromRoot=1):
+        '''
+        @arg path The path within the mounted database from which
+        to derive the root.
+
+        @arg params The parameters used to connect to the database.
+        No particular format required.
+        If there is more than one mount point referring to a
+        database, MountPoint will detect the matching params
+        and use the existing database.  Include the class name of
+        the storage.  For example,
+        ZEO params might be "ZODB.ZEOClient localhost 1081".
+
+        @arg classDefsFromRoot If true (the default), MountPoint will
+        try to get ZClass definitions from the root database rather
+        than the mounted database.
+        '''
+        # The only reason we need a __mountpoint_id is to
+        # be sure we don't close a database prematurely when
+        # it is mounted more than once and one of the points
+        # is unmounted.
+        self.__mountpoint_id = '%s_%f' % (id(self), time.time())
+        if params is None:
+            # We still need something to use as a hash in
+            # the "dbs" dictionary.
+            params = self.__mountpoint_id
+        self._params = repr(params)
+        self._path = path
+        self._classDefsFromRoot = classDefsFromRoot
+
+    def _createDB(self):
+        '''Gets the database object, usually by creating a Storage object
+        and returning ZODB.DB(storage).
+        '''
+        raise NotImplementedError
+
+    def _getDB(self):
+        '''Creates or opens a DB object.
+        '''
+        newMount = 0
+        dblock.acquire()
+        try:
+            params = self._params
+            dbInfo = dbs.get(params, None)
+            if dbInfo is None:
+                logger.info('Opening database for mounting: %s', params)
+                db = self._createDB()
+                newMount = 1
+                dbs[params] = (db, {self.__mountpoint_id:1})
+
+                if getattr(self, '_classDefsFromRoot', 1):
+                    db.classFactory = parentClassFactory
+            else:
+                db, mounts = dbInfo
+                # Be sure this object is in the list of mount points.
+                if not mounts.has_key(self.__mountpoint_id):
+                    newMount = 1
+                    mounts[self.__mountpoint_id] = 1
+            self._v_db = db
+        finally:
+            dblock.release()
+        return db, newMount
+
+    def _getMountpointId(self):
+        return self.__mountpoint_id
+
+    def _getMountParams(self):
+        return self._params
+
+    def __repr__(self):
+        return "%s(%s, %s)" % (self.__class__.__name__, repr(self._path),
+                               self._params)
+
+    def _openMountableConnection(self, parent):
+        # Opens a new connection to the database.
+        db = self._v_db
+        if db is None:
+            self._v_close_db = 0
+            db, newMount = self._getDB()
+        else:
+            newMount = 0
+        jar = getattr(self, '_p_jar', None)
+        if jar is None:
+            # Get _p_jar from parent.
+            self._p_jar = jar = parent._p_jar
+        conn = db.open(version=jar.getVersion())
+
+        # Add an attribute to the connection which
+        # makes it possible for us to find the primary
+        # database connection.  See ClassFactoryForMount().
+        conn._mount_parent_jar = jar
+
+        mcc = MountedConnectionCloser(self, conn)
+        jar.onCloseCallback(mcc)
+        return conn, newMount, mcc
+
+    def _getObjectFromConnection(self, conn):
+        obj = self._getMountRoot(conn.root())
+        data = aq_base(obj)
+        # Store the data object in a tuple to hide from acquisition.
+        self._v_data = (data,)
+        return data
+
+    def _getOrOpenObject(self, parent):
+        t = self._v_data
+        if t is None:
+            self._v_connect_error = None
+            conn = None
+            newMount = 0
+            mcc = None
+            try:
+                conn, newMount, mcc = self._openMountableConnection(parent)
+                data = self._getObjectFromConnection(conn)
+            except:
+                # Possibly broken database.
+                if mcc is not None:
+                    # Note that the next line may be a little rash--
+                    # if, for example, a working database throws an
+                    # exception rather than wait for a new connection,
+                    # this will likely cause the database to be closed
+                    # prematurely.  Perhaps DB.py needs a
+                    # countActiveConnections() method.
+                    mcc.setCloseDb()
+                logger.warning('Failed to mount database. %s (%s)', 
+                                exc_info=True)
+                raise
+            if newMount:
+                try: id = data.getId()
+                except: id = '???'  # data has no getId() method.  Bad.
+                p = '/'.join(parent.getPhysicalPath() + (id,))
+                logger.info('Mounted database %s at %s',
+                            self._getMountParams(), p)
+        else:
+            data = t[0]
+
+        return data.__of__(parent)
+
+    def __of__(self, parent):
+        # Accesses the database, returning an acquisition
+        # wrapper around the connected object rather than around self.
+        try:
+            return self._getOrOpenObject(parent)
+        except:
+            return ImplicitAcquisitionWrapper(self, parent)
+
+    def _test(self, parent):
+        '''Tests the database connection.
+        '''
+        self._getOrOpenObject(parent)
+        return 1
+
+    def _getMountRoot(self, root):
+        '''Gets the object to be mounted.
+        Can be overridden to provide different behavior.
+        '''
+        try:
+            app = root['Application']
+        except:
+            raise MountedStorageError(
+                "No 'Application' object exists in the mountable database.")
+        try:
+            return app.unrestrictedTraverse(self._path)
+        except:
+            raise MountedStorageError(
+                "The path '%s' was not found in the mountable database."
+                % self._path)
+
+
+class MountedConnectionCloser:
+    '''Closes the connection used by the mounted database
+    while performing other cleanup.
+    '''
+    close_db = 0
+
+    def __init__(self, mountpoint, conn):
+        # conn is the child connection.
+        self.mp = mountpoint
+        self.conn = conn
+
+    def setCloseDb(self):
+        self.close_db = 1
+
+    def __call__(self):
+        # The onCloseCallback handler.
+        # Closes a single connection to the database
+        # and possibly the database itself.
+        conn = self.conn
+        close_db = 0
+        if conn is not None:
+            mp = self.mp
+            # Remove potential circular references.
+            self.conn = None
+            self.mp = None
+            # Detect whether we should close the database.
+            close_db = self.close_db
+            t = mp.__dict__.get('_v_data', None)
+            if t is not None:
+                del mp.__dict__['_v_data']
+                data = t[0]
+                if not close_db and data.__dict__.get(
+                    '_v__object_deleted__', 0):
+                    # This mount point has been deleted.
+                    del data.__dict__['_v__object_deleted__']
+                    close_db = 1
+            # Close the child connection.
+            try:
+                del conn._mount_parent_jar
+            except:
+                pass
+            conn.close()
+
+        if close_db:
+            # Stop using this database. Close it if no other
+            # MountPoint is using it.
+            dblock.acquire()
+            try:
+                params = mp._getMountParams()
+                mp._v_db = None
+                if dbs.has_key(params):
+                    dbInfo = dbs[params]
+                    db, mounts = dbInfo
+                    try: del mounts[mp._getMountpointId()]
+                    except: pass
+                    if len(mounts) < 1:
+                        # No more mount points are using this database.
+                        del dbs[params]
+                        db.close()
+                        logger.info('Closed database: %s', params)
+            finally:
+                dblock.release()

Modified: Zope/trunk/lib/python/Products/ZODBMountPoint/MountedObject.py
===================================================================
--- Zope/trunk/lib/python/Products/ZODBMountPoint/MountedObject.py	2008-10-04 02:50:28 UTC (rev 91723)
+++ Zope/trunk/lib/python/Products/ZODBMountPoint/MountedObject.py	2008-10-04 03:30:21 UTC (rev 91724)
@@ -24,14 +24,15 @@
 
 import transaction
 
-import Globals
-import Acquisition
-from Acquisition import aq_base, aq_inner, aq_parent
+from App.class_init import default__class_init__ as InitializeClass
+from Acquisition import ImplicitAcquisitionWrapper
+from Acquisition import aq_base
+from Acquisition import aq_inner
+from Acquisition import aq_parent
 from AccessControl.ZopeGuards import guarded_getattr
 from OFS.SimpleItem import SimpleItem
 from OFS.Folder import Folder
 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
-from ZODB.POSException import MountedStorageError, ConnectionStateError
 
 LOG = getLogger('Zope.ZODBMountPoint')
 
@@ -215,11 +216,6 @@
     def _logConnectException(self):
         '''Records info about the exception that just occurred.
         '''
-        try:
-            from cStringIO import StringIO
-        except:
-            from StringIO import StringIO
-        import traceback
         exc = sys.exc_info()
         LOG.error('Failed to mount database. %s (%s)' % exc[:2], exc_info=exc)
         f=StringIO()
@@ -234,7 +230,7 @@
         try:
             return self._getOrOpenObject(parent)
         except:
-            return Acquisition.ImplicitAcquisitionWrapper(self, parent)
+            return ImplicitAcquisitionWrapper(self, parent)
 
 
     def _test(self, parent):
@@ -279,7 +275,7 @@
         return "%s(id=%s)" % (self.__class__.__name__, repr(self.id))
 
 
-Globals.InitializeClass(MountedObject)
+InitializeClass(MountedObject)
 
 
 def getMountPoint(ob):



More information about the Checkins mailing list