[Zodb-checkins] SVN: ZODB/branches/3.8/src/ZODB/Mount.py Added back the *untested* Mount module that is used by Zope 2.

Jim Fulton jim at zope.com
Wed Dec 19 13:44:18 EST 2007


Log message for revision 82361:
  Added back the *untested* Mount module that is used by Zope 2.
  This module needs to move to the Zope 2 tree.
  

Changed:
  A   ZODB/branches/3.8/src/ZODB/Mount.py

-=-
Copied: ZODB/branches/3.8/src/ZODB/Mount.py (from rev 82329, ZODB/branches/3.8/src/ZODB/Mount.py)
===================================================================
--- ZODB/branches/3.8/src/ZODB/Mount.py	                        (rev 0)
+++ ZODB/branches/3.8/src/ZODB/Mount.py	2007-12-19 18:44:17 UTC (rev 82361)
@@ -0,0 +1,304 @@
+##############################################################################
+#
+# 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$"""
+
+import time
+import thread
+import logging
+import persistent
+import Acquisition
+from Acquisition import aq_base
+from POSException import MountedStorageError
+
+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, Acquisition.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()
+                self._logConnectException()
+                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 Acquisition.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)
+
+    def _logConnectException(self):
+        '''Records info about the exception that just occurred.
+        '''
+        try:
+            from cStringIO import StringIO
+        except:
+            from StringIO import StringIO
+        import traceback
+        logger.warning('Failed to mount database. %s (%s)', exc[:2],
+                       exc_info=True)
+        f=StringIO()
+        traceback.print_tb(exc[2], 100, f)
+        self._v_connect_error = (exc[0], exc[1], f.getvalue())
+        exc = None
+
+
+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()



More information about the Zodb-checkins mailing list