Commit d9b010c4 authored by Hanno Schlichting's avatar Hanno Schlichting

Revert "Remove ZCacheable logic and StandardCacheManagers dependency."

This reverts part of commit dbb476e6.
parent b43972fe
...@@ -27,6 +27,8 @@ Features Added ...@@ -27,6 +27,8 @@ Features Added
Restructuring Restructuring
+++++++++++++ +++++++++++++
- Add back ZCacheable support.
- Update to zope.testbrowser 5.0 and its WebTest based implementation. - Update to zope.testbrowser 5.0 and its WebTest based implementation.
- Use `@implementer` and `@adapter` class decorators. - Use `@implementer` and `@adapter` class decorators.
......
...@@ -14,10 +14,19 @@ ...@@ -14,10 +14,19 @@
""" """
from logging import getLogger from logging import getLogger
import sys
import time
from AccessControl.class_init import InitializeClass from AccessControl.class_init import InitializeClass
from AccessControl.Permissions import view_management_screens from AccessControl.Permissions import view_management_screens
from AccessControl.SecurityInfo import ClassSecurityInfo from AccessControl.SecurityInfo import ClassSecurityInfo
from AccessControl.SecurityManagement import getSecurityManager
from AccessControl.unauthorized import Unauthorized
from Acquisition import aq_acquire
from Acquisition import aq_base
from Acquisition import aq_get
from Acquisition import aq_inner
from Acquisition import aq_parent
ZCM_MANAGERS = '__ZCacheManager_ids__' ZCM_MANAGERS = '__ZCacheManager_ids__'
...@@ -27,16 +36,18 @@ LOG = getLogger('Cache') ...@@ -27,16 +36,18 @@ LOG = getLogger('Cache')
def isCacheable(ob): def isCacheable(ob):
return False return getattr(aq_base(ob), '_isCacheable', 0)
def managersExist(ob): def managersExist(ob):
# Returns 1 if any CacheManagers exist in the context of ob. # Returns 1 if any CacheManagers exist in the context of ob.
return False if aq_get(ob, ZCM_MANAGERS, None, 1):
return 1
return 0
def filterCacheTab(ob): def filterCacheTab(ob):
return False return managersExist(ob)
def filterCacheManagers(orig, container, name, value, extra): def filterCacheManagers(orig, container, name, value, extra):
...@@ -45,14 +56,22 @@ def filterCacheManagers(orig, container, name, value, extra): ...@@ -45,14 +56,22 @@ def filterCacheManagers(orig, container, name, value, extra):
It causes objects to be found only if they are It causes objects to be found only if they are
in the list of cache managers. in the list of cache managers.
''' '''
return False if (hasattr(aq_base(container), ZCM_MANAGERS) and
name in getattr(container, ZCM_MANAGERS)):
return 1
return 0
def getVerifiedManagerIds(container): def getVerifiedManagerIds(container):
''' '''
Gets the list of cache managers in a container, verifying each one. Gets the list of cache managers in a container, verifying each one.
''' '''
return () ids = getattr(container, ZCM_MANAGERS, ())
rval = []
for id in ids:
if getattr(getattr(container, id, None), '_isCacheManager', 0):
rval.append(id)
return tuple(rval)
# Anytime a CacheManager is added or removed, all _v_ZCacheable_cache # Anytime a CacheManager is added or removed, all _v_ZCacheable_cache
...@@ -73,19 +92,42 @@ class Cacheable(object): ...@@ -73,19 +92,42 @@ class Cacheable(object):
_v_ZCacheable_cache = None _v_ZCacheable_cache = None
_v_ZCacheable_manager_timestamp = 0 _v_ZCacheable_manager_timestamp = 0
__manager_id = None __manager_id = None
__enabled = False __enabled = True
_isCacheable = False _isCacheable = True
security.declarePrivate('ZCacheable_getManager') security.declarePrivate('ZCacheable_getManager')
def ZCacheable_getManager(self): def ZCacheable_getManager(self):
'''Returns the currently associated cache manager.''' '''Returns the currently associated cache manager.'''
return None manager_id = self.__manager_id
if manager_id is None:
return None
try:
return aq_acquire(
self, manager_id, containment=1,
filter=filterCacheManagers, extra=None, default=None)
except AttributeError:
return None
security.declarePrivate('ZCacheable_getCache') security.declarePrivate('ZCacheable_getCache')
def ZCacheable_getCache(self): def ZCacheable_getCache(self):
'''Gets the cache associated with this object. '''Gets the cache associated with this object.
''' '''
return None if self.__manager_id is None:
return None
c = self._v_ZCacheable_cache
if c is not None:
# We have a volatile reference to the cache.
if self._v_ZCacheable_manager_timestamp == manager_timestamp:
return aq_base(c)
manager = self.ZCacheable_getManager()
if manager is not None:
c = aq_base(manager.ZCacheManager_getCache())
else:
return None
# Set a volatile reference to the cache then return it.
self._v_ZCacheable_cache = c
self._v_ZCacheable_manager_timestamp = manager_timestamp
return c
security.declarePrivate('ZCacheable_isCachingEnabled') security.declarePrivate('ZCacheable_isCachingEnabled')
def ZCacheable_isCachingEnabled(self): def ZCacheable_isCachingEnabled(self):
...@@ -93,7 +135,7 @@ class Cacheable(object): ...@@ -93,7 +135,7 @@ class Cacheable(object):
Returns true only if associated with a cache manager and Returns true only if associated with a cache manager and
caching of this method is enabled. caching of this method is enabled.
''' '''
return False return self.__enabled and self.ZCacheable_getCache()
security.declarePrivate('ZCacheable_getObAndView') security.declarePrivate('ZCacheable_getObAndView')
def ZCacheable_getObAndView(self, view_name): def ZCacheable_getObAndView(self, view_name):
...@@ -107,6 +149,16 @@ class Cacheable(object): ...@@ -107,6 +149,16 @@ class Cacheable(object):
conditions specified by keywords. If the value is conditions specified by keywords. If the value is
not yet cached, returns the default. not yet cached, returns the default.
''' '''
c = self.ZCacheable_getCache()
if c is not None and self.__enabled:
ob, view_name = self.ZCacheable_getObAndView(view_name)
try:
val = c.ZCache_get(ob, view_name, keywords,
mtime_func, default)
return val
except:
LOG.warn('ZCache_get() exception')
return default
return default return default
security.declarePrivate('ZCacheable_set') security.declarePrivate('ZCacheable_set')
...@@ -115,7 +167,14 @@ class Cacheable(object): ...@@ -115,7 +167,14 @@ class Cacheable(object):
'''Cacheable views should call this method after generating '''Cacheable views should call this method after generating
cacheable results. The data argument can be of any Python type. cacheable results. The data argument can be of any Python type.
''' '''
pass c = self.ZCacheable_getCache()
if c is not None and self.__enabled:
ob, view_name = self.ZCacheable_getObAndView(view_name)
try:
c.ZCache_set(ob, data, view_name, keywords,
mtime_func)
except:
LOG.warn('ZCache_set() exception')
security.declareProtected(ViewManagementScreensPermission, security.declareProtected(ViewManagementScreensPermission,
'ZCacheable_invalidate') 'ZCacheable_invalidate')
...@@ -124,23 +183,55 @@ class Cacheable(object): ...@@ -124,23 +183,55 @@ class Cacheable(object):
cache entries that apply to the view_name to be removed. cache entries that apply to the view_name to be removed.
Returns a status message. Returns a status message.
''' '''
pass c = self.ZCacheable_getCache()
if c is not None:
ob, view_name = self.ZCacheable_getObAndView(view_name)
try:
message = c.ZCache_invalidate(ob)
if not message:
message = 'Invalidated.'
except:
exc = sys.exc_info()
try:
LOG.warn('ZCache_invalidate() exception')
message = 'An exception occurred: %s: %s' % exc[:2]
finally:
exc = None
else:
message = 'This object is not associated with a cache manager.'
return message
security.declarePrivate('ZCacheable_getModTime') security.declarePrivate('ZCacheable_getModTime')
def ZCacheable_getModTime(self, mtime_func=None): def ZCacheable_getModTime(self, mtime_func=None):
'''Returns the highest of the last mod times.''' '''Returns the highest of the last mod times.'''
return 0 # Based on:
# mtime_func
# self.mtime
# self.__class__.mtime
mtime = 0
if mtime_func:
# Allow mtime_func to influence the mod time.
mtime = mtime_func()
base = aq_base(self)
mtime = max(getattr(base, '_p_mtime', mtime), mtime)
klass = getattr(base, '__class__', None)
if klass:
mtime = max(getattr(klass, '_p_mtime', mtime), mtime)
return mtime
security.declareProtected(ViewManagementScreensPermission, security.declareProtected(ViewManagementScreensPermission,
'ZCacheable_getManagerId') 'ZCacheable_getManagerId')
def ZCacheable_getManagerId(self): def ZCacheable_getManagerId(self):
'''Returns the id of the current ZCacheManager.''' '''Returns the id of the current ZCacheManager.'''
return None return self.__manager_id
security.declareProtected(ViewManagementScreensPermission, security.declareProtected(ViewManagementScreensPermission,
'ZCacheable_getManagerURL') 'ZCacheable_getManagerURL')
def ZCacheable_getManagerURL(self): def ZCacheable_getManagerURL(self):
'''Returns the URL of the current ZCacheManager.''' '''Returns the URL of the current ZCacheManager.'''
manager = self.ZCacheable_getManager()
if manager is not None:
return manager.absolute_url()
return None return None
security.declareProtected(ViewManagementScreensPermission, security.declareProtected(ViewManagementScreensPermission,
...@@ -148,32 +239,105 @@ class Cacheable(object): ...@@ -148,32 +239,105 @@ class Cacheable(object):
def ZCacheable_getManagerIds(self): def ZCacheable_getManagerIds(self):
'''Returns a list of mappings containing the id and title '''Returns a list of mappings containing the id and title
of the available ZCacheManagers.''' of the available ZCacheManagers.'''
return () rval = []
ob = self
used_ids = {}
while ob is not None:
if hasattr(aq_base(ob), ZCM_MANAGERS):
ids = getattr(ob, ZCM_MANAGERS)
for id in ids:
manager = getattr(ob, id, None)
if manager is not None:
id = manager.getId()
if id not in used_ids:
title = getattr(aq_base(manager), 'title', '')
rval.append({'id': id, 'title': title})
used_ids[id] = 1
ob = aq_parent(aq_inner(ob))
return tuple(rval)
security.declareProtected(ChangeCacheSettingsPermission, security.declareProtected(ChangeCacheSettingsPermission,
'ZCacheable_setManagerId') 'ZCacheable_setManagerId')
def ZCacheable_setManagerId(self, manager_id, REQUEST=None): def ZCacheable_setManagerId(self, manager_id, REQUEST=None):
'''Changes the manager_id for this object.''' '''Changes the manager_id for this object.'''
pass self.ZCacheable_invalidate()
if not manager_id:
# User requested disassociation
# from the cache manager.
manager_id = None
else:
manager_id = str(manager_id)
self.__manager_id = manager_id
self._v_ZCacheable_cache = None
security.declareProtected(ViewManagementScreensPermission, security.declareProtected(ViewManagementScreensPermission,
'ZCacheable_enabled') 'ZCacheable_enabled')
def ZCacheable_enabled(self): def ZCacheable_enabled(self):
'''Returns true if caching is enabled for this object '''Returns true if caching is enabled for this object
or method.''' or method.'''
return False return self.__enabled
security.declareProtected(ChangeCacheSettingsPermission, security.declareProtected(ChangeCacheSettingsPermission,
'ZCacheable_setEnabled') 'ZCacheable_setEnabled')
def ZCacheable_setEnabled(self, enabled=0, REQUEST=None): def ZCacheable_setEnabled(self, enabled=0, REQUEST=None):
'''Changes the enabled flag.''' '''Changes the enabled flag.'''
pass self.__enabled = enabled and 1 or 0
InitializeClass(Cacheable) InitializeClass(Cacheable)
class Cache: def findCacheables(ob, manager_id, require_assoc, subfolders,
meta_types, rval, path):
'''
Used by the CacheManager UI. Recursive. Similar to the Zope
"Find" function. Finds all Cacheable objects in a hierarchy.
'''
try:
if meta_types:
subobs = ob.objectValues(meta_types)
else:
subobs = ob.objectValues()
sm = getSecurityManager()
# Add to the list of cacheable objects.
for subob in subobs:
if not isCacheable(subob):
continue
associated = (subob.ZCacheable_getManagerId() == manager_id)
if require_assoc and not associated:
continue
if not sm.checkPermission('Change cache settings', subob):
continue
subpath = path + (subob.getId(),)
info = {
'sortkey': subpath,
'path': '/'.join(subpath),
'title': getattr(aq_base(subob), 'title', ''),
'icon': None,
'associated': associated,
}
rval.append(info)
# Visit subfolders.
if subfolders:
if meta_types:
subobs = ob.objectValues()
for subob in subobs:
subpath = path + (subob.getId(),)
if hasattr(aq_base(subob), 'objectValues'):
if sm.checkPermission(
'Access contents information', subob):
findCacheables(
subob, manager_id, require_assoc,
subfolders, meta_types, rval, subpath)
except:
# Ignore exceptions.
import traceback
traceback.print_exc()
class Cache(object):
''' '''
A base class (and interface description) for caches. A base class (and interface description) for caches.
Note that Cache objects are not intended to be visible by Note that Cache objects are not intended to be visible by
...@@ -207,7 +371,7 @@ class Cache: ...@@ -207,7 +371,7 @@ class Cache:
raise NotImplementedError raise NotImplementedError
class CacheManager: class CacheManager(object):
''' '''
A base class for cache managers. Implement ZCacheManager_getCache(). A base class for cache managers. Implement ZCacheManager_getCache().
''' '''
...@@ -225,11 +389,27 @@ class CacheManager: ...@@ -225,11 +389,27 @@ class CacheManager:
def manage_afterAdd(self, item, container): def manage_afterAdd(self, item, container):
# Adds self to the list of cache managers in the container. # Adds self to the list of cache managers in the container.
pass if aq_base(self) is aq_base(item):
ids = getVerifiedManagerIds(container)
id = self.getId()
if id not in ids:
setattr(container, ZCM_MANAGERS, ids + (id,))
global manager_timestamp
manager_timestamp = time.time()
def manage_beforeDelete(self, item, container): def manage_beforeDelete(self, item, container):
# Removes self from the list of cache managers. # Removes self from the list of cache managers.
pass if aq_base(self) is aq_base(item):
ids = getVerifiedManagerIds(container)
id = self.getId()
if id in ids:
manager_ids = [s for s in ids if s != id]
if manager_ids:
setattr(container, ZCM_MANAGERS, manager_ids)
elif getattr(aq_base(self), ZCM_MANAGERS, None) is not None:
delattr(self, ZCM_MANAGERS)
global manager_timestamp
manager_timestamp = time.time()
security.declareProtected(ChangeCacheSettingsPermission, security.declareProtected(ChangeCacheSettingsPermission,
'ZCacheManager_locate') 'ZCacheManager_locate')
...@@ -237,7 +417,15 @@ class CacheManager: ...@@ -237,7 +417,15 @@ class CacheManager:
meta_types=[], REQUEST=None): meta_types=[], REQUEST=None):
'''Locates cacheable objects. '''Locates cacheable objects.
''' '''
return [] ob = aq_parent(aq_inner(self))
rval = []
manager_id = self.getId()
if '' in meta_types:
# User selected "All".
meta_types = []
findCacheables(ob, manager_id, require_assoc, subfolders,
meta_types, rval, ())
return rval
security.declareProtected(ChangeCacheSettingsPermission, security.declareProtected(ChangeCacheSettingsPermission,
'ZCacheManager_setAssociations') 'ZCacheManager_setAssociations')
...@@ -245,6 +433,30 @@ class CacheManager: ...@@ -245,6 +433,30 @@ class CacheManager:
'''Associates and un-associates cacheable objects with this '''Associates and un-associates cacheable objects with this
cache manager. cache manager.
''' '''
pass addcount = 0
remcount = 0
parent = aq_parent(aq_inner(self))
sm = getSecurityManager()
my_id = str(self.getId())
if props is None:
props = REQUEST.form
for key, do_associate in props.items():
if key[:10] == 'associate_':
path = key[10:]
ob = parent.restrictedTraverse(path)
if not sm.checkPermission('Change cache settings', ob):
raise Unauthorized
if not isCacheable(ob):
# Not a cacheable object.
continue
manager_id = str(ob.ZCacheable_getManagerId())
if do_associate:
if manager_id != my_id:
ob.ZCacheable_setManagerId(my_id)
addcount = addcount + 1
else:
if manager_id == my_id:
ob.ZCacheable_setManagerId(None)
remcount = remcount + 1
InitializeClass(CacheManager) InitializeClass(CacheManager)
...@@ -60,6 +60,7 @@ class DTMLDocument(PropertyManager, DTMLMethod): ...@@ -60,6 +60,7 @@ class DTMLDocument(PropertyManager, DTMLMethod):
file = file.read() file = file.read()
self.munge(file) self.munge(file)
self.ZCacheable_invalidate()
if REQUEST: if REQUEST:
message = "Content uploaded." message = "Content uploaded."
return self.manage_main(self, REQUEST, manage_tabs_message=message) return self.manage_main(self, REQUEST, manage_tabs_message=message)
...@@ -69,6 +70,12 @@ class DTMLDocument(PropertyManager, DTMLMethod): ...@@ -69,6 +70,12 @@ class DTMLDocument(PropertyManager, DTMLMethod):
o If supplied, use REQUEST mapping, Response, and key word arguments. o If supplied, use REQUEST mapping, Response, and key word arguments.
""" """
if not self._cache_namespace_keys:
data = self.ZCacheable_get(default=_marker)
if data is not _marker:
# Return cached results.
return data
__traceback_supplement__ = (PathTracebackSupplement, self) __traceback_supplement__ = (PathTracebackSupplement, self)
kw['document_id'] = self.getId() kw['document_id'] = self.getId()
kw['document_title'] = self.title kw['document_title'] = self.title
...@@ -88,11 +95,15 @@ class DTMLDocument(PropertyManager, DTMLMethod): ...@@ -88,11 +95,15 @@ class DTMLDocument(PropertyManager, DTMLMethod):
result = r result = r
else: else:
result = decapitate(r, RESPONSE) result = decapitate(r, RESPONSE)
if not self._cache_namespace_keys:
self.ZCacheable_set(result)
return result return result
r = HTML.__call__(self, (client, bself), REQUEST, **kw) r = HTML.__call__(self, (client, bself), REQUEST, **kw)
if RESPONSE is None or not isinstance(r, str): if RESPONSE is None or not isinstance(r, str):
if not self._cache_namespace_keys:
self.ZCacheable_set(r)
return r return r
finally: finally:
...@@ -106,6 +117,8 @@ class DTMLDocument(PropertyManager, DTMLMethod): ...@@ -106,6 +117,8 @@ class DTMLDocument(PropertyManager, DTMLMethod):
c, e = guess_content_type(self.__name__, r) c, e = guess_content_type(self.__name__, r)
RESPONSE.setHeader('Content-Type', c) RESPONSE.setHeader('Content-Type', c)
result = decapitate(r, RESPONSE) result = decapitate(r, RESPONSE)
if not self._cache_namespace_keys:
self.ZCacheable_set(result)
return result return result
......
...@@ -37,6 +37,8 @@ from OFS import bbb ...@@ -37,6 +37,8 @@ from OFS import bbb
from OFS.Cache import Cacheable from OFS.Cache import Cacheable
from OFS.role import RoleManager from OFS.role import RoleManager
from OFS.SimpleItem import Item_w__name__ from OFS.SimpleItem import Item_w__name__
from ZPublisher.Iterators import IStreamIterator
if sys.version_info >= (3, ): if sys.version_info >= (3, ):
basestring = str basestring = str
...@@ -59,6 +61,7 @@ class DTMLMethod(RestrictedDTML, ...@@ -59,6 +61,7 @@ class DTMLMethod(RestrictedDTML,
""" """
meta_type = 'DTML Method' meta_type = 'DTML Method'
index_html = None # Prevent accidental acquisition index_html = None # Prevent accidental acquisition
_cache_namespace_keys = ()
security = ClassSecurityInfo() security = ClassSecurityInfo()
security.declareObjectProtected(View) security.declareObjectProtected(View)
...@@ -72,7 +75,8 @@ class DTMLMethod(RestrictedDTML, ...@@ -72,7 +75,8 @@ class DTMLMethod(RestrictedDTML,
{'label': 'Edit', 'action': 'manage_main'}, {'label': 'Edit', 'action': 'manage_main'},
) + ) +
RoleManager.manage_options + RoleManager.manage_options +
Item_w__name__.manage_options Item_w__name__.manage_options +
Cacheable.manage_options
) )
# More reasonable default for content-type for http HEAD requests. # More reasonable default for content-type for http HEAD requests.
...@@ -88,6 +92,27 @@ class DTMLMethod(RestrictedDTML, ...@@ -88,6 +92,27 @@ class DTMLMethod(RestrictedDTML,
o If supplied, use the REQUEST mapping, Response, and key word o If supplied, use the REQUEST mapping, Response, and key word
arguments. arguments.
""" """
if not self._cache_namespace_keys:
data = self.ZCacheable_get(default=_marker)
if data is not _marker:
if (IStreamIterator.isImplementedBy(data) and
RESPONSE is not None):
# This is a stream iterator and we need to set some
# headers now before giving it to medusa
headers_get = RESPONSE.headers.get
if headers_get('content-length', None) is None:
RESPONSE.setHeader('content-length', len(data))
if (headers_get('content-type', None) is None and
headers_get('Content-type', None) is None):
ct = (self.__dict__.get('content_type') or
self.default_content_type)
RESPONSE.setHeader('content-type', ct)
# Return cached results.
return data
__traceback_supplement__ = (PathTracebackSupplement, self) __traceback_supplement__ = (PathTracebackSupplement, self)
kw['document_id'] = self.getId() kw['document_id'] = self.getId()
kw['document_title'] = self.title kw['document_title'] = self.title
...@@ -108,10 +133,14 @@ class DTMLMethod(RestrictedDTML, ...@@ -108,10 +133,14 @@ class DTMLMethod(RestrictedDTML,
result = r result = r
else: else:
result = decapitate(r, RESPONSE) result = decapitate(r, RESPONSE)
if not self._cache_namespace_keys:
self.ZCacheable_set(result)
return result return result
r = HTML.__call__(self, client, REQUEST, **kw) r = HTML.__call__(self, client, REQUEST, **kw)
if RESPONSE is None or not isinstance(r, str): if RESPONSE is None or not isinstance(r, str):
if not self._cache_namespace_keys:
self.ZCacheable_set(r)
return r return r
finally: finally:
...@@ -127,24 +156,54 @@ class DTMLMethod(RestrictedDTML, ...@@ -127,24 +156,54 @@ class DTMLMethod(RestrictedDTML,
c, e = guess_content_type(self.getId(), r) c, e = guess_content_type(self.getId(), r)
RESPONSE.setHeader('Content-Type', c) RESPONSE.setHeader('Content-Type', c)
result = decapitate(r, RESPONSE) result = decapitate(r, RESPONSE)
if not self._cache_namespace_keys:
self.ZCacheable_set(result)
return result return result
def validate(self, inst, parent, name, value, md=None): def validate(self, inst, parent, name, value, md=None):
return getSecurityManager().validate(inst, parent, name, value) return getSecurityManager().validate(inst, parent, name, value)
def ZDocumentTemplate_beforeRender(self, md, default): def ZDocumentTemplate_beforeRender(self, md, default):
# Tries to get a cached value.
if self._cache_namespace_keys:
# Use the specified keys from the namespace to identify a
# cache entry.
kw = {}
for key in self._cache_namespace_keys:
try:
val = md[key]
except:
val = None
kw[key] = val
return self.ZCacheable_get(keywords=kw, default=default)
return default return default
def ZDocumentTemplate_afterRender(self, md, result): def ZDocumentTemplate_afterRender(self, md, result):
pass # Tries to set a cache value.
if self._cache_namespace_keys:
kw = {}
for key in self._cache_namespace_keys:
try:
val = md[key]
except:
val = None
kw[key] = val
self.ZCacheable_set(result, keywords=kw)
security.declareProtected(change_dtml_methods, 'getCacheNamespaceKeys') security.declareProtected(change_dtml_methods, 'getCacheNamespaceKeys')
def getCacheNamespaceKeys(self): def getCacheNamespaceKeys(self):
return () # Return the cacheNamespaceKeys.
return self._cache_namespace_keys
security.declareProtected(change_dtml_methods, 'setCacheNamespaceKeys') security.declareProtected(change_dtml_methods, 'setCacheNamespaceKeys')
def setCacheNamespaceKeys(self, keys, REQUEST=None): def setCacheNamespaceKeys(self, keys, REQUEST=None):
pass # Set the list of names looked up to provide a cache key.
ks = []
for key in keys:
key = str(key).strip()
if key:
ks.append(key)
self._cache_namespace_keys = tuple(ks)
security.declareProtected(View, 'get_size') security.declareProtected(View, 'get_size')
def get_size(self): def get_size(self):
...@@ -178,6 +237,7 @@ class DTMLMethod(RestrictedDTML, ...@@ -178,6 +237,7 @@ class DTMLMethod(RestrictedDTML,
if not isinstance(data, basestring): if not isinstance(data, basestring):
data = data.read() data = data.read()
self.munge(data) self.munge(data)
self.ZCacheable_invalidate()
if REQUEST: if REQUEST:
message = "Saved changes." message = "Saved changes."
return self.manage_main(self, REQUEST, manage_tabs_message=message) return self.manage_main(self, REQUEST, manage_tabs_message=message)
...@@ -195,6 +255,7 @@ class DTMLMethod(RestrictedDTML, ...@@ -195,6 +255,7 @@ class DTMLMethod(RestrictedDTML,
file = file.read() file = file.read()
self.munge(file) self.munge(file)
self.ZCacheable_invalidate()
if REQUEST: if REQUEST:
message = "Saved changes." message = "Saved changes."
return self.manage_main(self, REQUEST, manage_tabs_message=message) return self.manage_main(self, REQUEST, manage_tabs_message=message)
...@@ -220,6 +281,7 @@ class DTMLMethod(RestrictedDTML, ...@@ -220,6 +281,7 @@ class DTMLMethod(RestrictedDTML,
self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1) self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
body = REQUEST.get('BODY', '') body = REQUEST.get('BODY', '')
self.munge(body) self.munge(body)
self.ZCacheable_invalidate()
RESPONSE.setStatus(204) RESPONSE.setStatus(204)
return RESPONSE return RESPONSE
......
...@@ -112,7 +112,8 @@ class File(Persistent, Implicit, PropertyManager, ...@@ -112,7 +112,8 @@ class File(Persistent, Implicit, PropertyManager,
manage_options = ( manage_options = (
({'label': 'Edit', 'action': 'manage_main'}, ) + ({'label': 'Edit', 'action': 'manage_main'}, ) +
RoleManager.manage_options + RoleManager.manage_options +
Item_w__name__.manage_options Item_w__name__.manage_options +
Cacheable.manage_options
) )
_properties = ( _properties = (
...@@ -372,6 +373,13 @@ class File(Persistent, Implicit, PropertyManager, ...@@ -372,6 +373,13 @@ class File(Persistent, Implicit, PropertyManager,
""" """
if self._if_modified_since_request_handler(REQUEST, RESPONSE): if self._if_modified_since_request_handler(REQUEST, RESPONSE):
# we were able to handle this by returning a 304
# unfortunately, because the HTTP cache manager uses the cache
# API, and because 304 responses are required to carry the Expires
# header for HTTP/1.1, we need to call ZCacheable_set here.
# This is nonsensical for caches other than the HTTP cache manager
# unfortunately.
self.ZCacheable_set(None)
return '' return ''
if self.precondition and hasattr(self, str(self.precondition)): if self.precondition and hasattr(self, str(self.precondition)):
...@@ -393,6 +401,17 @@ class File(Persistent, Implicit, PropertyManager, ...@@ -393,6 +401,17 @@ class File(Persistent, Implicit, PropertyManager,
RESPONSE.setHeader('Content-Length', self.size) RESPONSE.setHeader('Content-Length', self.size)
RESPONSE.setHeader('Accept-Ranges', 'bytes') RESPONSE.setHeader('Accept-Ranges', 'bytes')
if self.ZCacheable_isCachingEnabled():
result = self.ZCacheable_get(default=None)
if result is not None:
# We will always get None from RAMCacheManager and HTTP
# Accelerated Cache Manager but we will get
# something implementing the IStreamIterator interface
# from a "FileCacheManager"
return result
self.ZCacheable_set(None)
data = self.data data = self.data
if isinstance(data, str): if isinstance(data, str):
RESPONSE.setBase(None) RESPONSE.setBase(None)
...@@ -430,6 +449,8 @@ class File(Persistent, Implicit, PropertyManager, ...@@ -430,6 +449,8 @@ class File(Persistent, Implicit, PropertyManager,
size = len(data) size = len(data)
self.size = size self.size = size
self.data = data self.data = data
self.ZCacheable_invalidate()
self.ZCacheable_set(None)
self.http__refreshEtag() self.http__refreshEtag()
security.declareProtected(change_images_and_files, 'manage_edit') security.declareProtected(change_images_and_files, 'manage_edit')
...@@ -449,6 +470,8 @@ class File(Persistent, Implicit, PropertyManager, ...@@ -449,6 +470,8 @@ class File(Persistent, Implicit, PropertyManager,
del self.precondition del self.precondition
if filedata is not None: if filedata is not None:
self.update_data(filedata, content_type, len(filedata)) self.update_data(filedata, content_type, len(filedata))
else:
self.ZCacheable_invalidate()
notify(ObjectModifiedEvent(self)) notify(ObjectModifiedEvent(self))
...@@ -609,6 +632,18 @@ class File(Persistent, Implicit, PropertyManager, ...@@ -609,6 +632,18 @@ class File(Persistent, Implicit, PropertyManager,
def manage_FTPget(self): def manage_FTPget(self):
"""Return body for ftp.""" """Return body for ftp."""
RESPONSE = self.REQUEST.RESPONSE RESPONSE = self.REQUEST.RESPONSE
if self.ZCacheable_isCachingEnabled():
result = self.ZCacheable_get(default=None)
if result is not None:
# We will always get None from RAMCacheManager but we will
# get something implementing the IStreamIterator interface
# from FileCacheManager.
# the content-length is required here by HTTPResponse,
# even though FTP doesn't use it.
RESPONSE.setHeader('Content-Length', self.size)
return result
data = self.data data = self.data
if isinstance(data, str): if isinstance(data, str):
RESPONSE.setBase(None) RESPONSE.setBase(None)
...@@ -765,7 +800,8 @@ class Image(File): ...@@ -765,7 +800,8 @@ class Image(File):
manage_options = ( manage_options = (
({'label': 'Edit', 'action': 'manage_main'}, ) + ({'label': 'Edit', 'action': 'manage_main'}, ) +
RoleManager.manage_options + RoleManager.manage_options +
Item_w__name__.manage_options Item_w__name__.manage_options +
Cacheable.manage_options
) )
manage_editForm = DTMLFile('dtml/imageEdit', globals(), manage_editForm = DTMLFile('dtml/imageEdit', globals(),
...@@ -803,6 +839,8 @@ class Image(File): ...@@ -803,6 +839,8 @@ class Image(File):
if content_type is not None: if content_type is not None:
self.content_type = content_type self.content_type = content_type
self.ZCacheable_invalidate()
self.ZCacheable_set(None)
self.http__refreshEtag() self.http__refreshEtag()
def __str__(self): def __str__(self):
......
...@@ -4,4 +4,7 @@ ...@@ -4,4 +4,7 @@
<five:deprecatedManageAddDelete <five:deprecatedManageAddDelete
class="OFS.userfolder.BasicUserFolder"/> class="OFS.userfolder.BasicUserFolder"/>
<five:deprecatedManageAddDelete
class="OFS.Cache.CacheManager"/>
</configure> </configure>
import unittest
from OFS.Cache import CacheManager
from OFS.Folder import Folder
from OFS.SimpleItem import SimpleItem
from OFS.metaconfigure import setDeprecatedManageAddDelete
class DummyCacheManager(CacheManager, SimpleItem):
def __init__(self, id, *args, **kw):
self.id = id
setDeprecatedManageAddDelete(DummyCacheManager)
class CacheTests(unittest.TestCase):
def test_managersExist(self):
from OFS.Cache import managersExist
from OFS.DTMLMethod import DTMLMethod
root = Folder('root')
root._setObject('root_cache', DummyCacheManager('root_cache'))
root._setObject('child', Folder('child'))
root.child._setObject('child_cache', DummyCacheManager('child_cache'))
root.child._setObject('child_content', DTMLMethod('child_content'))
# To begin with, cache managers will be found correctly
# using managersExist
self.assertTrue(managersExist(root.child.child_content))
# Now we delete the cache in the child folder
root.child.manage_delObjects(['child_cache'])
# The parent_cache should still trigger managersExist
self.assertTrue(managersExist(root.child.child_content))
...@@ -10,6 +10,8 @@ from io import BytesIO ...@@ -10,6 +10,8 @@ from io import BytesIO
from Acquisition import aq_base from Acquisition import aq_base
from OFS.Application import Application from OFS.Application import Application
from OFS.SimpleItem import SimpleItem
from OFS.Cache import ZCM_MANAGERS
from OFS.Image import Pdata from OFS.Image import Pdata
from ZPublisher.HTTPRequest import HTTPRequest from ZPublisher.HTTPRequest import HTTPRequest
from ZPublisher.HTTPResponse import HTTPResponse from ZPublisher.HTTPResponse import HTTPResponse
...@@ -50,6 +52,41 @@ def aputrequest(file, content_type): ...@@ -50,6 +52,41 @@ def aputrequest(file, content_type):
return req return req
class DummyCache(object):
def __init__(self):
self.clear()
def ZCache_set(self, ob, data, view_name='', keywords=None,
mtime_func=None):
self.set = (ob, data)
def ZCache_get(self, ob, data, view_name='', keywords=None,
mtime_func=None):
self.get = ob
if self.si:
return self.si
def ZCache_invalidate(self, ob):
self.invalidated = ob
def clear(self):
self.set = None
self.get = None
self.invalidated = None
self.si = None
def setStreamIterator(self, si):
self.si = si
ADummyCache = DummyCache()
class DummyCacheManager(SimpleItem):
def ZCacheManager_getCache(self):
return ADummyCache
class EventCatcher(object): class EventCatcher(object):
def __init__(self): def __init__(self):
...@@ -97,9 +134,13 @@ class FileTests(unittest.TestCase): ...@@ -97,9 +134,13 @@ class FileTests(unittest.TestCase):
self.root = a self.root = a
responseOut = self.responseOut = BytesIO() responseOut = self.responseOut = BytesIO()
self.app = makerequest(self.root, stdout=responseOut) self.app = makerequest(self.root, stdout=responseOut)
self.app.dcm = DummyCacheManager()
factory = getattr(self.app, self.factory) factory = getattr(self.app, self.factory)
factory('file', factory('file',
file=self.data, content_type=self.content_type) file=self.data, content_type=self.content_type)
self.app.file.ZCacheable_setManagerId('dcm')
self.app.file.ZCacheable_setEnabled(enabled=1)
setattr(self.app, ZCM_MANAGERS, ('dcm',))
# Hack, we need a _p_mtime for the file, so we make sure that it # Hack, we need a _p_mtime for the file, so we make sure that it
# has one. # has one.
transaction.commit() transaction.commit()
...@@ -128,6 +169,7 @@ class FileTests(unittest.TestCase): ...@@ -128,6 +169,7 @@ class FileTests(unittest.TestCase):
del self.responseOut del self.responseOut
del self.root del self.root
del self.connection del self.connection
ADummyCache.clear()
self.eventCatcher.tearDown() self.eventCatcher.tearDown()
def testViewImageOrFile(self): def testViewImageOrFile(self):
...@@ -137,6 +179,8 @@ class FileTests(unittest.TestCase): ...@@ -137,6 +179,8 @@ class FileTests(unittest.TestCase):
self.file.update_data('foo') self.file.update_data('foo')
self.assertEqual(self.file.size, 3) self.assertEqual(self.file.size, 3)
self.assertEqual(self.file.data, 'foo') self.assertEqual(self.file.data, 'foo')
self.assertTrue(ADummyCache.invalidated)
self.assertTrue(ADummyCache.set)
def testReadData(self): def testReadData(self):
s = "a" * (2 << 16) s = "a" * (2 << 16)
...@@ -161,6 +205,8 @@ class FileTests(unittest.TestCase): ...@@ -161,6 +205,8 @@ class FileTests(unittest.TestCase):
self.file.manage_edit('foobar', 'text/plain', filedata='ASD') self.file.manage_edit('foobar', 'text/plain', filedata='ASD')
self.assertEqual(self.file.title, 'foobar') self.assertEqual(self.file.title, 'foobar')
self.assertEqual(self.file.content_type, 'text/plain') self.assertEqual(self.file.content_type, 'text/plain')
self.assertTrue(ADummyCache.invalidated)
self.assertTrue(ADummyCache.set)
self.assertEqual(1, len(self.eventCatcher.modified)) self.assertEqual(1, len(self.eventCatcher.modified))
self.assertTrue(self.eventCatcher.modified[0].object is self.file) self.assertTrue(self.eventCatcher.modified[0].object is self.file)
...@@ -168,6 +214,7 @@ class FileTests(unittest.TestCase): ...@@ -168,6 +214,7 @@ class FileTests(unittest.TestCase):
self.file.manage_edit('foobar', 'text/plain') self.file.manage_edit('foobar', 'text/plain')
self.assertEqual(self.file.title, 'foobar') self.assertEqual(self.file.title, 'foobar')
self.assertEqual(self.file.content_type, 'text/plain') self.assertEqual(self.file.content_type, 'text/plain')
self.assertTrue(ADummyCache.invalidated)
self.assertEqual(1, len(self.eventCatcher.modified)) self.assertEqual(1, len(self.eventCatcher.modified))
self.assertTrue(self.eventCatcher.modified[0].object is self.file) self.assertTrue(self.eventCatcher.modified[0].object is self.file)
...@@ -256,6 +303,8 @@ class ImageTests(FileTests): ...@@ -256,6 +303,8 @@ class ImageTests(FileTests):
self.assertEqual(self.file.data, self.data) self.assertEqual(self.file.data, self.data)
self.assertEqual(self.file.width, 16) self.assertEqual(self.file.width, 16)
self.assertEqual(self.file.height, 16) self.assertEqual(self.file.height, 16)
self.assertTrue(ADummyCache.invalidated)
self.assertTrue(ADummyCache.set)
def testStr(self): def testStr(self):
self.assertEqual( self.assertEqual(
......
...@@ -88,7 +88,7 @@ class ZopePageTemplate(Script, PageTemplate, Cacheable, ...@@ -88,7 +88,7 @@ class ZopePageTemplate(Script, PageTemplate, Cacheable,
manage_options = ( manage_options = (
{'label': 'Edit', 'action': 'pt_editForm'}, {'label': 'Edit', 'action': 'pt_editForm'},
) + SimpleItem.manage_options ) + SimpleItem.manage_options + Cacheable.manage_options
_properties = ( _properties = (
{'id': 'title', 'type': 'ustring', 'mode': 'w'}, {'id': 'title', 'type': 'ustring', 'mode': 'w'},
...@@ -164,6 +164,7 @@ class ZopePageTemplate(Script, PageTemplate, Cacheable, ...@@ -164,6 +164,7 @@ class ZopePageTemplate(Script, PageTemplate, Cacheable,
if not is_unicode: if not is_unicode:
text = unicode(text, encoding) text = unicode(text, encoding)
self.ZCacheable_invalidate()
super(ZopePageTemplate, self).pt_edit(text, content_type) super(ZopePageTemplate, self).pt_edit(text, content_type)
pt_editForm = PageTemplateFile('www/ptEdit', globals(), pt_editForm = PageTemplateFile('www/ptEdit', globals(),
...@@ -203,6 +204,11 @@ class ZopePageTemplate(Script, PageTemplate, Cacheable, ...@@ -203,6 +204,11 @@ class ZopePageTemplate(Script, PageTemplate, Cacheable,
title = unicode(title, encoding) title = unicode(title, encoding)
self._setPropValue('title', title) self._setPropValue('title', title)
def _setPropValue(self, id, value):
""" set a property and invalidate the cache """
PropertyManager._setPropValue(self, id, value)
self.ZCacheable_invalidate()
security.declareProtected(change_page_templates, 'pt_upload') security.declareProtected(change_page_templates, 'pt_upload')
def pt_upload(self, REQUEST, file='', encoding='utf-8'): def pt_upload(self, REQUEST, file='', encoding='utf-8'):
"""Replace the document with the text in file.""" """Replace the document with the text in file."""
...@@ -248,6 +254,7 @@ class ZopePageTemplate(Script, PageTemplate, Cacheable, ...@@ -248,6 +254,7 @@ class ZopePageTemplate(Script, PageTemplate, Cacheable,
preferred_encodings) preferred_encodings)
self.output_encoding = encoding self.output_encoding = encoding
self.ZCacheable_invalidate()
ZopePageTemplate.inheritedAttribute('write')(self, text) ZopePageTemplate.inheritedAttribute('write')(self, text)
def _exec(self, bound_names, args, kw): def _exec(self, bound_names, args, kw):
...@@ -265,11 +272,25 @@ class ZopePageTemplate(Script, PageTemplate, Cacheable, ...@@ -265,11 +272,25 @@ class ZopePageTemplate(Script, PageTemplate, Cacheable,
security = getSecurityManager() security = getSecurityManager()
bound_names['user'] = security.getUser() bound_names['user'] = security.getUser()
# Retrieve the value from the cache.
keyset = None
if self.ZCacheable_isCachingEnabled():
# Prepare a cache key.
keyset = {'here': self._getContext(),
'bound_names': bound_names}
result = self.ZCacheable_get(keywords=keyset)
if result is not None:
# Got a cached value.
return result
# Execute the template in a new security context. # Execute the template in a new security context.
security.addContext(self) security.addContext(self)
try: try:
result = self.pt_render(extra_context=bound_names) result = self.pt_render(extra_context=bound_names)
if keyset is not None:
# Store the result in the cache.
self.ZCacheable_set(result, keywords=keyset)
return result return result
finally: finally:
security.removeContext(self) security.removeContext(self)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment