[CMF-checkins] SVN: CMF/trunk/C - CMFCore.CachingPolicyManager:
Implemented the old OFS.Cache.CacheManager
Jens Vagelpohl
jens at dataflake.org
Sun Nov 12 17:13:59 EST 2006
Log message for revision 71114:
- CMFCore.CachingPolicyManager: Implemented the old OFS.Cache.CacheManager
API. Now objects other than CMF content or CMF templates can have their
caching headers set by the caching policy manager with the same
fine-grained control.
(http://www.zope.org/Collectors/CMF/408)
Changed:
U CMF/trunk/CHANGES.txt
U CMF/trunk/CMFCore/CachingPolicyManager.py
U CMF/trunk/CMFCore/event.zcml
U CMF/trunk/CMFCore/tests/test_CachingPolicyManager.py
-=-
Modified: CMF/trunk/CHANGES.txt
===================================================================
--- CMF/trunk/CHANGES.txt 2006-11-12 21:41:20 UTC (rev 71113)
+++ CMF/trunk/CHANGES.txt 2006-11-12 22:13:58 UTC (rev 71114)
@@ -2,6 +2,12 @@
New Features
+ - CMFCore.CachingPolicyManager: Implemented the old OFS.Cache.CacheManager
+ API. Now objects other than CMF content or CMF templates can have their
+ caching headers set by the caching policy manager with the same
+ fine-grained control.
+ (http://www.zope.org/Collectors/CMF/408)
+
- testing: Added test layers for setting up ZCML.
- CMFDefault formlib: Added zope.formlib support.
Modified: CMF/trunk/CMFCore/CachingPolicyManager.py
===================================================================
--- CMF/trunk/CMFCore/CachingPolicyManager.py 2006-11-12 21:41:20 UTC (rev 71113)
+++ CMF/trunk/CMFCore/CachingPolicyManager.py 2006-11-12 22:13:58 UTC (rev 71114)
@@ -21,9 +21,15 @@
from Globals import DTMLFile
from Globals import InitializeClass
from Globals import PersistentMapping
+from OFS.Cache import Cache
+from OFS.Cache import CacheManager
+from OFS.Cache import getVerifiedManagerIds
+from OFS.Cache import ZCM_MANAGERS
+from OFS.interfaces import IObjectWillBeMovedEvent
from OFS.SimpleItem import SimpleItem
from Products.PageTemplates.Expressions import getEngine
from Products.PageTemplates.Expressions import SecureModuleImporter
+from zope.app.container.interfaces import IObjectMovedEvent
from zope.interface import implements
from permissions import ManagePortal
@@ -34,9 +40,22 @@
from interfaces.CachingPolicyManager \
import CachingPolicyManager as z2ICachingPolicyManager
from utils import _dtmldir
+from utils import _setCacheHeaders
+from utils import _ViewEmulator
from utils import getToolByName
+# This is lame :(
+# This listing is used to decide whether to wrap an object inside a "fake view"
+# for the OFS.Cache caching. If it is a view type, no fake view wrap is needed.
+VIEW_METATYPES = (
+ 'Page Template',
+ 'DTML Method',
+ 'DTML Document',
+ 'Filesystem DTML Method',
+ 'Filesystem Page Template',
+ )
+
def createCPContext( content, view_method, keywords, time=None ):
"""
Construct an expression context for TALES expressions,
@@ -66,7 +85,42 @@
return getEngine().getContext( data )
+class CPMCache(Cache):
+ """ Simple OFS.Cache-implementation
+ """
+ security = ClassSecurityInfo()
+ security.declarePrivate('ZCache_invalidate')
+ def ZCache_invalidate(self, ob):
+ """ An object is forced out of the cache
+
+ This implementation stores nothing and does not attempt to
+ communicate with cache servers, so this is a no-op.
+ """
+ pass
+
+ security.declarePrivate('ZCache_get')
+ def ZCache_get(self, ob, view_name, keywords, mtime_func, default):
+ """ An object is retrieved from the cache
+
+ This implementation stores nothing - a no-op.
+ """
+ pass
+
+ security.declarePrivate('ZCache_set')
+ def ZCache_set(self, ob, data, view_name, keywords, mtime_func):
+ """ An object is pushed into the cache
+
+ Even though this cache implementation does not cache anything per se,
+ this method is used as a suitable hook to activate the real heavy
+ lifting done by the CachePolicyManager.
+ """
+ if ob.meta_type not in VIEW_METATYPES:
+ ob = _ViewEmulator().__of__(ob)
+
+ return _setCacheHeaders(ob, extra_context={})
+
+
class CachingPolicy:
"""
Represent a single class of cachable objects:
@@ -398,7 +452,7 @@
-class CachingPolicyManager( SimpleItem ):
+class CachingPolicyManager( SimpleItem, CacheManager ):
"""
Manage the set of CachingPolicy objects for the site; dispatch
to them from skin methods.
@@ -409,6 +463,7 @@
id = 'caching_policy_manager'
meta_type = 'CMF Caching Policy Manager'
+ _isCacheManager = 1 # Dead chicken. Yum.
security = ClassSecurityInfo()
@@ -425,6 +480,7 @@
}
,
)
+ + CacheManager.manage_options
+ SimpleItem.manage_options
)
@@ -803,10 +859,47 @@
return None
+ #
+ # OFS.CacheManager API
+ #
+ security.declarePrivate('ZCacheManager_getCache')
+ def ZCacheManager_getCache(self):
+ """ Retrieve a cache object
+ """
+ cache = getattr(self, '_cache', None)
+ if cache is None:
+ self._cache = CPMCache()
+ cache = self._cache
+
+ return cache
+
+
InitializeClass( CachingPolicyManager )
+def handleCachingPolicyManagerEvent(ob, event):
+ """ Event subscriber for (un)registering a CPM as CacheManager
+ """
+ if not ICachingPolicyManager.providedBy(ob):
+ return
+
+ if IObjectMovedEvent.providedBy(event):
+ if event.newParent is not None:
+ ids = getVerifiedManagerIds(event.newParent)
+ id = ob.getId()
+ if id not in ids:
+ setattr(event.newParent, ZCM_MANAGERS, ids + (id,))
+
+ elif IObjectWillBeMovedEvent.providedBy(event):
+ if event.oldParent is not None:
+ ids = list(getVerifiedManagerIds(event.oldParent))
+ id = ob.getId()
+ if id in ids:
+ ids.remove(id)
+ setattr(event.oldParent, ZCM_MANAGERS, tuple(ids))
+
+
def manage_addCachingPolicyManager( self, REQUEST=None ):
"""
Add a CPM to self.
Modified: CMF/trunk/CMFCore/event.zcml
===================================================================
--- CMF/trunk/CMFCore/event.zcml 2006-11-12 21:41:20 UTC (rev 71113)
+++ CMF/trunk/CMFCore/event.zcml 2006-11-12 22:13:58 UTC (rev 71114)
@@ -2,6 +2,12 @@
xmlns="http://namespaces.zope.org/zope">
<subscriber
+ for=".interfaces.ICachingPolicyManager
+ zope.component.interfaces.IObjectEvent"
+ handler=".CachingPolicyManager.handleCachingPolicyManagerEvent"
+ />
+
+ <subscriber
for=".interfaces.ICookieCrumbler
zope.component.interfaces.IObjectEvent"
handler=".CookieCrumbler.handleCookieCrumblerEvent"
Modified: CMF/trunk/CMFCore/tests/test_CachingPolicyManager.py
===================================================================
--- CMF/trunk/CMFCore/tests/test_CachingPolicyManager.py 2006-11-12 21:41:20 UTC (rev 71113)
+++ CMF/trunk/CMFCore/tests/test_CachingPolicyManager.py 2006-11-12 22:13:58 UTC (rev 71114)
@@ -23,8 +23,10 @@
from os.path import join as path_join
from AccessControl.SecurityManagement import newSecurityManager
+from Acquisition import Implicit
from App.Common import rfc1123_date
from DateTime.DateTime import DateTime
+from OFS.Cache import Cacheable
from Products.CMFCore.FSPageTemplate import FSPageTemplate
from Products.CMFCore.FSDTMLMethod import FSDTMLMethod
@@ -54,7 +56,39 @@
def modified( self ):
return self.modified
+class CacheableDummyContent(Implicit, Cacheable):
+ __allow_access_to_unprotected_subobjects__ = 1
+
+ def __init__(self, id):
+ self.id = id
+ self.modified = DateTime()
+
+ def getId(self):
+ """ """
+ return self.id
+
+ def modified( self ):
+ return self.modified
+
+ def __call__(self):
+ """ """
+ 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)
+
+class DummyView(CacheableDummyContent):
+
+ meta_type = 'DTML Method'
+
+
class CachingPolicyTests(unittest.TestCase):
layer = TraversingZCMLLayer
@@ -1173,12 +1207,122 @@
)
+class OFSCacheTests(RequestTest):
+
+ layer = FunctionalZCMLLayer
+
+ def setUp(self):
+ from Products.CMFCore import CachingPolicyManager
+
+ RequestTest.setUp(self)
+
+ # Create a fake portal and the tools we need
+ self.portal = DummySite(id='portal').__of__(self.root)
+ self.portal._setObject('doc1', CacheableDummyContent('doc1'))
+ self.portal._setObject('doc2', CacheableDummyContent('doc2'))
+ CachingPolicyManager.manage_addCachingPolicyManager(self.portal)
+ cpm = self.portal.caching_policy_manager
+
+ # This policy only applies to doc1. It will not emit any ETag header
+ # but it enables If-modified-since handling.
+ cpm.addPolicy(policy_id = 'policy_1',
+ predicate = 'python:object.getId()=="doc1"',
+ mtime_func = '',
+ max_age_secs = 100,
+ no_cache = 0,
+ no_store = 0,
+ must_revalidate = 0,
+ vary = 'doc1',
+ etag_func = '',
+ enable_304s = 0)
+
+ def test_empty(self):
+
+ from Products.CMFCore.CachingPolicyManager import CPMCache
+
+ cpm = self.portal.caching_policy_manager
+ doc1 = self.portal.doc1
+ self.failUnless(cpm._isCacheManager)
+ self.failUnless(isinstance(cpm.ZCacheManager_getCache(), CPMCache))
+ self.assertEquals( doc1.ZCacheable_getManagerIds()
+ , ({'id':cpm.getId(), 'title':''},)
+ )
+
+ def test_no_association(self):
+ # Render an item that would match the CPM policy, but don't
+ # associate it with the CPM.
+ self.portal.doc1()
+
+ # no headers should be added by the CPM if all is well
+ headers = [x.lower() for x in self.RESPONSE.headers.keys()]
+ self.failIf('x-cache-headers-set-by' in headers)
+ self.failIf('vary' in headers)
+
+ def test_unsuitable_association(self):
+ # Render an item that is associated with the CPM, but that does not
+ # match any policy.
+ cpm = self.portal.caching_policy_manager
+ doc2 = self.portal.doc2
+ doc2.ZCacheable_setManagerId(cpm.getId())
+
+ doc2()
+
+ # no headers should be added by the CPM if all is well
+ headers = [x.lower() for x in self.RESPONSE.headers.keys()]
+ self.failIf('x-cache-headers-set-by' in headers)
+ self.failIf('vary' in headers)
+
+ def test_suitable_association(self):
+ # Render a content item that will trigger the CPM
+ cpm = self.portal.caching_policy_manager
+ doc1 = self.portal.doc1
+ doc1.ZCacheable_setManagerId(cpm.getId())
+
+ doc1()
+
+ # Policy "policy_1" should have triggered
+ # Just to be sure, change headers so they are definitely all
+ # lower-cased
+ headers = {}
+ header_info = self.RESPONSE.headers.items()
+ [headers.__setitem__(x[0].lower(), x[1]) for x in header_info]
+
+ self.failUnless(headers.get('x-cache-headers-set-by'))
+ self.assertEquals(headers.get('vary'), 'doc1')
+ self.assertEquals( headers.get('cache-control')
+ , 'max-age=100'
+ )
+
+ def test_with_view(self):
+ # Render a view for a content item that will trigger the CPM
+ cpm = self.portal.caching_policy_manager
+ self.portal._setObject('a_view', DummyView(id='a_view'))
+ self.portal.a_view.ZCacheable_setManagerId(cpm.getId())
+ doc1 = self.portal.doc1
+
+ doc1.a_view()
+
+ # Policy "policy_1" should have triggered
+ # Just to be sure, change headers so they are definitely all
+ # lower-cased
+ headers = {}
+ header_info = self.RESPONSE.headers.items()
+ [headers.__setitem__(x[0].lower(), x[1]) for x in header_info]
+
+ self.failUnless(headers.get('x-cache-headers-set-by'))
+ self.assertEquals(headers.get('vary'), 'doc1')
+ self.assertEquals( headers.get('cache-control')
+ , 'max-age=100'
+ )
+
+
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(CachingPolicyTests),
unittest.makeSuite(CachingPolicyManagerTests),
unittest.makeSuite(CachingPolicyManager304Tests),
unittest.makeSuite(NestedTemplateTests),
+ unittest.makeSuite(OFSCacheTests),
))
if __name__ == '__main__':
More information about the CMF-checkins
mailing list