Log message for revision 100881: Backported r91724 from the trunk: - 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.
Changed: U Zope/branches/2.11/doc/CHANGES.txt U Zope/branches/2.11/lib/python/Products/TemporaryFolder/TemporaryFolder.py A Zope/branches/2.11/lib/python/Products/TemporaryFolder/mount.py U Zope/branches/2.11/lib/python/Products/ZODBMountPoint/MountedObject.py -=- Modified: Zope/branches/2.11/doc/CHANGES.txt =================================================================== --- Zope/branches/2.11/doc/CHANGES.txt 2009-06-11 17:30:25 UTC (rev 100880) +++ Zope/branches/2.11/doc/CHANGES.txt 2009-06-11 17:53:20 UTC (rev 100881) @@ -4,6 +4,20 @@ Change information for previous versions of Zope can be found in the file HISTORY.txt. + After Zope 2.11.3 + + 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. + Zope 2.11.3 (2009/05/04) Features added Modified: Zope/branches/2.11/lib/python/Products/TemporaryFolder/TemporaryFolder.py =================================================================== --- Zope/branches/2.11/lib/python/Products/TemporaryFolder/TemporaryFolder.py 2009-06-11 17:30:25 UTC (rev 100880) +++ Zope/branches/2.11/lib/python/Products/TemporaryFolder/TemporaryFolder.py 2009-06-11 17:53:20 UTC (rev 100881) @@ -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 """ Copied: Zope/branches/2.11/lib/python/Products/TemporaryFolder/mount.py (from rev 91724, Zope/trunk/lib/python/Products/TemporaryFolder/mount.py) =================================================================== --- Zope/branches/2.11/lib/python/Products/TemporaryFolder/mount.py (rev 0) +++ Zope/branches/2.11/lib/python/Products/TemporaryFolder/mount.py 2009-06-11 17:53:20 UTC (rev 100881) @@ -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/branches/2.11/lib/python/Products/ZODBMountPoint/MountedObject.py =================================================================== --- Zope/branches/2.11/lib/python/Products/ZODBMountPoint/MountedObject.py 2009-06-11 17:30:25 UTC (rev 100880) +++ Zope/branches/2.11/lib/python/Products/ZODBMountPoint/MountedObject.py 2009-06-11 17:53:20 UTC (rev 100881) @@ -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): _______________________________________________ Zope-Checkins maillist - Zope-Checkins@zope.org http://mail.zope.org/mailman/listinfo/zope-checkins