[Zope-Checkins] CVS: Zope/lib/python/Products/ZODBMountPoint - Mount.py:1.1.2.1 MountedObject.py:1.1.2.1 __init__.py:1.1.2.1
Chris McDonough
chrism@zope.com
Mon, 21 Jul 2003 12:36:39 -0400
Update of /cvs-repository/Zope/lib/python/Products/ZODBMountPoint
In directory cvs.zope.org:/tmp/cvs-serv17213/lib/python/Products/ZODBMountPoint
Added Files:
Tag: Zope-2_7-branch
Mount.py MountedObject.py __init__.py
Log Message:
Merge changes from HEAD since the release of Zope 2.7a1 into the Zope-2_7-branch in preparation for release of Zope 2.7b1.
=== Added File Zope/lib/python/Products/ZODBMountPoint/Mount.py ===
##############################################################################
#
# 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
#
##############################################################################
"""ZODB Mounted database support, simplified for DBTab.
$Id: Mount.py,v 1.1.2.1 2003/07/21 16:36:30 chrism Exp $"""
import time, sys
import Persistence, Acquisition
from Acquisition import aq_base
from ZODB.POSException import MountedStorageError
from zLOG import LOG, ERROR, INFO, WARNING
class MountPoint(Persistence.Persistent, Acquisition.Implicit):
'''The base class for a Zope object which, when traversed,
accesses a different database.
'''
# Default values for non-persistent variables.
_v_data = None # An object in an open connection
_v_connect_error = None
def __init__(self, id):
self.id = id
def _getDB(self):
"""Hook for getting the DB object for this mount point.
"""
raise NotImplementedError
def _getDBName(self):
"""Hook for getting the name of the database for this mount point.
"""
raise NotImplementedError
def _getRootDBName(self):
"""Hook for getting the name of the root database.
"""
raise NotImplementedError
def _traverseToMountedRoot(self, root, mount_parent):
"""Hook for getting the object to be mounted.
"""
raise NotImplementedError
def __repr__(self):
return "%s(id=%s)" % (self.__class__.__name__, repr(self.id))
def _getMountedConnection(self, anyjar):
db_name = self._getDBName()
conn = anyjar._getMountedConnection(db_name)
if conn is None:
root_conn = anyjar._getRootConnection()
if db_name == self._getRootDBName():
conn = root_conn
else:
conn = self._getDB().open(version=root_conn.getVersion())
root_conn._addMountedConnection(db_name, conn)
return conn
def _getOrOpenObject(self, parent):
t = self._v_data
if t is not None:
data = t[0]
else:
self._v_connect_error = None
conn = None
try:
anyjar = self._p_jar
if anyjar is None:
anyjar = parent._p_jar
conn = self._getMountedConnection(anyjar)
root = conn.root()
obj = self._traverseToMountedRoot(root, parent)
data = aq_base(obj)
# Store the data object in a tuple to hide from acquisition.
self._v_data = (data,)
except:
# Possibly broken database.
self._logConnectException()
raise
try:
# XXX This method of finding the mount point is deprecated.
# Do not use the _v_mount_point_ attribute.
data._v_mount_point_ = (aq_base(self),)
except:
# Might be a read-only object.
pass
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 _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('ZODB', ERROR, 'Failed to mount database. %s (%s)' % exc[:2],
error=exc)
f=StringIO()
traceback.print_tb(exc[2], 100, f)
self._v_connect_error = (exc[0], exc[1], f.getvalue())
exc = None
class ConnectionPatches:
# Changes to Connection.py that might fold into ZODB
_root_connection = None
_mounted_connections = None
def _getRootConnection(self):
root_conn = self._root_connection
if root_conn is None:
return self
else:
return root_conn
def _getMountedConnection(self, name):
conns = self._getRootConnection()._mounted_connections
if conns is None:
return None
else:
return conns.get(name)
def _addMountedConnection(self, name, conn):
if conn._root_connection is not None:
raise ValueError, 'Connection %s is already mounted' % repr(conn)
root_conn = self._getRootConnection()
conns = root_conn._mounted_connections
if conns is None:
conns = {}
root_conn._mounted_connections = conns
if conns.has_key(name):
raise KeyError, 'A connection named %s already exists' % repr(name)
conn._root_connection = root_conn
conns[name] = conn
def _setDB(self, odb):
self._real_setDB(odb)
conns = self._mounted_connections
if conns:
for conn in conns.values():
conn._setDB(conn._db)
def close(self):
if self._root_connection is not None:
raise RuntimeError("Should not close mounted connections directly")
conns = self._mounted_connections
if conns:
for conn in conns.values():
# Notify the activity monitor
db = conn.db()
f = getattr(db, 'getActivityMonitor', None)
if f is not None:
am = f()
if am is not None:
am.closedConnection(conn)
conn._incrgc() # This is a good time to do some GC
# XXX maybe we ought to call the close callbacks.
conn._storage = conn._tmp = conn.new_oid = conn._opened = None
conn._debug_info = ()
# The mounted connection keeps a reference to
# its database, but nothing else.
# Note that mounted connections can not operate
# independently, so don't use _closeConnection() to
# return them to the pool. Only the root connection
# should be returned.
# Close this connection only after the mounted connections
# have been closed. Otherwise, this connection gets returned
# to the pool too early and another thread might use this
# connection before the mounted connections have all been
# closed.
self._real_close()
if 1:
# patch Connection.py.
from ZODB.Connection import Connection
Connection._real_setDB = Connection._setDB
Connection._real_close = Connection.close
for k, v in ConnectionPatches.__dict__.items():
setattr(Connection, k, v)
=== Added File Zope/lib/python/Products/ZODBMountPoint/MountedObject.py ===
##############################################################################
#
# 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
#
##############################################################################
"""DBTab mount point (stored in ZODB).
$Id: MountedObject.py,v 1.1.2.1 2003/07/21 16:36:30 chrism Exp $
"""
import os
import Globals
from Acquisition import aq_base, aq_inner, 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 Mount import MountPoint
_www = os.path.join(os.path.dirname(__file__), 'www')
configuration = None
def getConfiguration():
from App.config import getConfiguration
global configuration
if configuration is None:
configuration = getConfiguration().dbtab
return configuration
def setConfiguration(c):
global configuration
configuration = c
class SimpleTrailblazer:
"""Follows Zope paths. If a path is not found, creates a Folder.
Respects Zope security.
"""
restricted = 1
def __init__(self, base):
self.base = base
def _construct(self, context, id, final):
"""Creates and returns the named folder."""
dispatcher = guarded_getattr(context, 'manage_addProduct')['OFSP']
factory = guarded_getattr(dispatcher, 'manage_addFolder')
factory(id)
o = context.restrictedTraverse(id)
# Commit a subtransaction to assign the new object to
# the correct database.
get_transaction().commit(1)
return o
def traverseOrConstruct(self, path, omit_final=0):
"""Traverses a path, constructing it if necessary."""
container = self.base
parts = filter(None, path.split('/'))
if omit_final:
if len(parts) < 1:
raise ValueError, 'Path %s is not a valid mount path' % path
parts = parts[:-1]
for part in parts:
try:
if self.restricted:
container = container.restrictedTraverse(part)
else:
container = container.unrestrictedTraverse(part)
except (KeyError, AttributeError):
# Try to create a container in this place.
container = self._construct(container, part)
return container
class CustomTrailblazer (SimpleTrailblazer):
"""Like SimpleTrailblazer but creates custom objects.
Does not respect Zope security because this may be invoked before
security and products get initialized.
"""
restricted = 0
def __init__(self, base, container_class=None):
self.base = base
if not container_class:
container_class = 'OFS.Folder.Folder'
pos = container_class.rfind('.')
if pos < 0:
raise ValueError("Not a valid container_class: %s" % repr(
container_class))
self.module_name = container_class[:pos]
self.class_name = container_class[pos + 1:]
def _construct(self, context, id):
"""Creates and returns the named object."""
jar = self.base._p_jar
klass = jar.db()._classFactory(jar, self.module_name, self.class_name)
obj = klass(id)
obj._setId(id)
context._setObject(id, obj)
obj = context.unrestrictedTraverse(id)
# Commit a subtransaction to assign the new object to
# the correct database.
get_transaction().commit(1)
return obj
class MountedObject(MountPoint, SimpleItem):
'''A MountPoint with a basic interface for displaying the
reason the database did not connect.
'''
meta_type = 'ZODB Mount Point'
_isMountedObject = 1
_create_mount_points = 0
icon = 'p_/broken'
manage_options = ({'label':'Traceback', 'action':'manage_traceback'},)
_v_mount_params = None
manage_traceback = PageTemplateFile('mountfail.pt', _www)
def __init__(self, path):
path = str(path)
self._path = path
id = path.split('/')[-1]
MountPoint.__init__(self, id)
def mount_error_(self):
return self._v_connect_error
def _getDB(self):
"""Hook for getting the DB object for this mount point.
"""
return getConfiguration().getDatabase(self._path)
def _getDBName(self):
"""Hook for getting the name of the database for this mount point.
"""
return getConfiguration().getDatabaseFactory(self._path).getName()
def _getRootDBName(self):
"""Hook for getting the name of the root database.
"""
return getConfiguration().getDatabaseFactory('/').getName()
def _loadMountParams(self):
factory = getConfiguration().getDatabaseFactory(self._path)
params = factory.getMountParams(self._path)
self._v_mount_params = params
return params
def _traverseToMountedRoot(self, root, mount_parent):
"""Hook for getting the object to be mounted.
"""
params = self._v_mount_params
if params is None:
params = self._loadMountParams()
real_root, real_path, container_class = params
if real_root is None:
real_root = 'Application'
try:
obj = root[real_root]
except KeyError:
if container_class or self._create_mount_points:
# Create a database automatically.
from OFS.Application import Application
obj = Application()
root[real_root] = obj
# Get it into the database
get_transaction().commit(1)
else:
raise
if real_path is None:
real_path = self._path
if real_path and real_path != '/':
try:
obj = obj.unrestrictedTraverse(real_path)
except (KeyError, AttributeError):
if container_class or self._create_mount_points:
blazer = CustomTrailblazer(obj, container_class)
obj = blazer.traverseOrConstruct(real_path)
else:
raise
return obj
Globals.InitializeClass(MountedObject)
def getMountPoint(ob):
"""Gets the mount point for a mounted object.
Returns None if the object is not a mounted object.
"""
container = aq_parent(aq_inner(ob))
mps = getattr(container, '_mount_points', None)
if mps:
mp = mps.get(ob.getId())
if mp is not None and (mp._p_jar is ob._p_jar or ob._p_jar is None):
# Since the mount point and the mounted object are from
# the same connection, the mount point must have been
# replaced. The object is not mounted after all.
return None
# else the object is mounted.
return mp
return None
def setMountPoint(container, id, mp):
mps = getattr(container, '_mount_points', None)
if mps is None:
container._mount_points = {id: aq_base(mp)}
else:
container._p_changed = 1
mps[id] = aq_base(mp)
manage_addMountsForm = PageTemplateFile('addMountsForm.pt', _www)
def manage_getMountStatus(dispatcher):
"""Returns the status of each mount point specified by dbtab.conf.
"""
res = []
conf = getConfiguration()
items = conf.listMountPaths()
items.sort()
root = dispatcher.getPhysicalRoot()
for path, name in items:
if not path or path == '/':
# Ignore the root mount.
continue
o = root.unrestrictedTraverse(path, None)
# Examine the _v_mount_point_ attribute to verify traversal
# to the correct mount point.
if o is None:
exists = 0
status = 'Ready to create'
elif getattr(o, '_isMountedObject', 0):
# Oops, didn't actually mount!
exists = 1
t, v = o._v_connect_error[:2]
status = '%s: %s' % (t, v)
else:
exists = 1
mp = getMountPoint(o)
if mp is None:
mp_old = getattr(o, '_v_mount_point_', None)
if mp_old is not None:
# Use the old method of accessing mount points
# to update to the new method.
# Update the container right now.
setMountPoint(dispatcher.this(), o.getId(), mp_old[0])
status = 'Ok (updated)'
else:
status = '** Something is in the way **'
else:
mp_path = getattr(mp, '_path', None)
if mp_path != path:
status = '** Set to wrong path: %s **' % repr(mp_path)
else:
status = 'Ok'
res.append({
'path': path, 'name': name, 'exists': exists,
'status': status,
})
return res
def manage_addMounts(dispatcher, paths=(), create_mount_points=0,
REQUEST=None):
"""Adds MountedObjects at the requested paths.
"""
count = 0
app = dispatcher.getPhysicalRoot()
for path in paths:
mo = MountedObject(path)
mo._create_mount_points = not not create_mount_points
# Raise an error now if there is any problem.
mo._test(app)
blazer = SimpleTrailblazer(app)
container = blazer.traverseOrConstruct(path, omit_final=1)
mo._p_jar = container._p_jar
loaded = mo.__of__(container)
# Add a faux object to avoid generating manage_afterAdd() events
# while appeasing OFS.ObjectManager._setObject(), then discreetly
# replace the faux object with a MountedObject.
faux = Folder()
faux.id = mo.id
faux.meta_type = loaded.meta_type
container._setObject(faux.id, faux)
del mo._create_mount_points
container._setOb(faux.id, mo)
setMountPoint(container, faux.id, mo)
count += 1
if REQUEST is not None:
REQUEST['RESPONSE'].redirect(
REQUEST['URL1'] + ('/manage_main?manage_tabs_message='
'Added %d mount points.' % count))
=== Added File Zope/lib/python/Products/ZODBMountPoint/__init__.py ===
##############################################################################
#
# 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
#
##############################################################################
"""ZODBMountPoint product.
$Id: __init__.py,v 1.1.2.1 2003/07/21 16:36:30 chrism Exp $
"""
def initialize(context):
# Configure and load databases if not already done.
import MountedObject
context.registerClass(
MountedObject.MountedObject,
constructors=(MountedObject.manage_addMountsForm,
MountedObject.manage_getMountStatus,
MountedObject.manage_addMounts,),
)