Back to index

plone3  3.1.7
CachingPolicyManager.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
00004 #
00005 # This software is subject to the provisions of the Zope Public License,
00006 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
00007 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00008 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00009 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00010 # FOR A PARTICULAR PURPOSE.
00011 #
00012 ##############################################################################
00013 """Caching tool implementation.
00014 
00015 $Id: CachingPolicyManager.py 77186 2007-06-28 19:06:19Z yuppie $
00016 """
00017 
00018 from AccessControl import ClassSecurityInfo
00019 from App.Common import rfc1123_date
00020 from DateTime.DateTime import DateTime
00021 from Globals import DTMLFile
00022 from Globals import InitializeClass
00023 from Globals import PersistentMapping
00024 from OFS.Cache import Cache
00025 from OFS.Cache import CacheManager
00026 from OFS.Cache import getVerifiedManagerIds
00027 from OFS.Cache import ZCM_MANAGERS
00028 from OFS.interfaces import IObjectWillBeMovedEvent
00029 from OFS.SimpleItem import SimpleItem
00030 from Products.PageTemplates.Expressions import getEngine
00031 from Products.PageTemplates.Expressions import SecureModuleImporter
00032 from zope.app.container.interfaces import IObjectMovedEvent
00033 from zope.interface import implements
00034 
00035 from Expression import Expression
00036 from interfaces import ICachingPolicy
00037 from interfaces import ICachingPolicyManager
00038 from interfaces.CachingPolicyManager \
00039         import CachingPolicyManager as z2ICachingPolicyManager
00040 from permissions import ManagePortal
00041 from permissions import View
00042 from utils import _dtmldir
00043 from utils import _setCacheHeaders
00044 from utils import _ViewEmulator
00045 from utils import getToolByName
00046 from utils import registerToolInterface
00047 
00048 # This is lame :(
00049 # This listing is used to decide whether to wrap an object inside a "fake view"
00050 # for the OFS.Cache caching. If it is a view type, no fake view wrap is needed.
00051 VIEW_METATYPES = (
00052     'Page Template',
00053     'DTML Method',
00054     'DTML Document',
00055     'Filesystem DTML Method',
00056     'Filesystem Page Template',
00057     )
00058 
00059 
00060 def createCPContext( content, view_method, keywords, time=None ):
00061     """
00062         Construct an expression context for TALES expressions,
00063         for use by CachingPolicy objects.
00064     """
00065     pm = getToolByName( content, 'portal_membership', None )
00066     if not pm or pm.isAnonymousUser():
00067         member = None
00068     else:
00069         member = pm.getAuthenticatedMember()
00070 
00071     if time is None:
00072         time = DateTime()
00073 
00074     # The name "content" is deprecated and will go away in CMF 2.0,
00075     # please use "object" in your policy
00076     data = { 'content'  : content
00077            , 'object'   : content
00078            , 'view'     : view_method
00079            , 'keywords' : keywords
00080            , 'request'  : getattr( content, 'REQUEST', {} )
00081            , 'member'   : member
00082            , 'modules'  : SecureModuleImporter
00083            , 'nothing'  : None
00084            , 'time'     : time
00085            }
00086 
00087     return getEngine().getContext( data )
00088 
00089 class CPMCache(Cache):
00090     """ Simple OFS.Cache-implementation
00091     """
00092     security = ClassSecurityInfo()
00093 
00094     security.declarePrivate('ZCache_invalidate')
00095     def ZCache_invalidate(self, ob):
00096         """ An object is forced out of the cache
00097 
00098         This implementation stores nothing and does not attempt to 
00099         communicate with cache servers, so this is a no-op.
00100         """
00101         pass
00102 
00103     security.declarePrivate('ZCache_get')
00104     def ZCache_get(self, ob, view_name, keywords, mtime_func, default):
00105         """ An object is retrieved from the cache
00106 
00107         This implementation stores nothing - a no-op.
00108         """
00109         pass
00110 
00111     security.declarePrivate('ZCache_set')
00112     def ZCache_set(self, ob, data, view_name, keywords, mtime_func):
00113         """ An object is pushed into the cache
00114 
00115         Even though this cache implementation does not cache anything per se,
00116         this method is used as a suitable hook to activate the real heavy
00117         lifting done by the CachePolicyManager.
00118         """
00119         if ob.meta_type not in VIEW_METATYPES:
00120             ob = _ViewEmulator().__of__(ob)
00121 
00122         return _setCacheHeaders(ob, extra_context={})
00123 
00124 
00125 class CachingPolicy:
00126     """
00127         Represent a single class of cachable objects:
00128 
00129           - class membership is defined by 'predicate', a TALES expression
00130             with access to the following top-level names:
00131 
00132             'object' -- the object itself
00133 
00134             'view' -- the name of the view method
00135 
00136             'keywords' -- keywords passed to the request
00137 
00138             'request' -- the REQUEST object itself
00139 
00140             'member' -- the authenticated member, or None if anonymous
00141 
00142             'modules' -- usual TALES access-with-import
00143 
00144             'nothing' -- None
00145 
00146             'time' -- A DateTime object for the current date and time
00147 
00148           - mtime_func is used to set the "Last-modified" HTTP response
00149             header, which is another TALES expression evaluated
00150             against the same namespace.  If not specified explicitly,
00151             uses 'object/modified'.  mtime_func is also used in responding
00152             to conditional GETs.
00153 
00154           - The "Expires" HTTP response header and the "max-age" token of
00155             the "Cache-control" header will be set using 'max_age_secs',
00156             if passed;  it should be an integer value in seconds.
00157 
00158           - The "s-maxage" token of the "Cache-control" header will be
00159             set using 's_max_age_secs', if passed;  it should be an integer
00160             value in seconds.
00161 
00162           - The "Vary" HTTP response headers will be set if a value is 
00163             provided. The Vary header is described in RFC 2616. In essence,
00164             it instructs caches that respect this header (such as Squid
00165             after version 2.4) to distinguish between requests not just by
00166             the request URL, but also by values found in the headers showing
00167             in the Vary tag. "Vary: Cookie" would force Squid to also take 
00168             Cookie headers into account when deciding what cached object to 
00169             choose and serve in response to a request.
00170 
00171           - The "ETag" HTTP response header will be set if a value is
00172             provided. The value is a TALES expression and the result 
00173             after evaluation will be used as the ETag header value.
00174 
00175           - Other tokens will be added to the "Cache-control" HTTP response
00176             header as follows:
00177 
00178              'no_cache=1' argument => "no-cache" token
00179 
00180              'no_store=1' argument => "no-store" token
00181 
00182              'must_revalidate=1' argument => "must-revalidate" token
00183 
00184              'proxy_revalidate=1' argument => "proxy-revalidate" token
00185 
00186              'public=1' argument => "public" token
00187 
00188              'private=1' argument => "private" token
00189 
00190              'no_transform=1' argument => "no-transform" token
00191 
00192           - The last_modified argument is used to determine whether to add a
00193             Last-Modified header.  last_modified=1 by default.  There appears
00194             to be a bug in IE 6 (and possibly other versions) that uses the
00195             Last-Modified header plus some heuristics rather than the other
00196             explicit caching headers to determine whether to render content
00197             from the cache.  If you set, say, max-age=0, must-revalidate and
00198             have a Last-Modified header some time in the past, IE will
00199             recognize that the page in cache is stale and will request an
00200             update from the server BUT if you have a Last-Modified header
00201             with an older date, will then ignore the update and render from
00202             the cache, so you may want to disable the Last-Modified header
00203             when controlling caching using Cache-Control headers.
00204 
00205           - The pre-check and post-check Cache-Control tokens are Microsoft
00206             proprietary tokens added to IE 5+.  Documentation can be found
00207             here: http://msdn.microsoft.com/workshop/author/perf/perftips.asp
00208             Unfortunately these are needed to make IE behave correctly.
00209 
00210     """
00211 
00212     implements(ICachingPolicy)
00213 
00214     def __init__( self
00215                 , policy_id
00216                 , predicate=''
00217                 , mtime_func=''
00218                 , max_age_secs=None
00219                 , no_cache=0
00220                 , no_store=0
00221                 , must_revalidate=0
00222                 , vary=''
00223                 , etag_func=''
00224                 , s_max_age_secs=None
00225                 , proxy_revalidate=0
00226                 , public=0
00227                 , private=0
00228                 , no_transform=0
00229                 , enable_304s=0
00230                 , last_modified=1
00231                 , pre_check=None
00232                 , post_check=None
00233                 ):
00234 
00235         if not predicate:
00236             predicate = 'python:1'
00237 
00238         if not mtime_func:
00239             mtime_func = 'object/modified'
00240 
00241         if max_age_secs is not None:
00242             if str(max_age_secs).strip() == '':
00243                 max_age_secs = None
00244             else:
00245                 max_age_secs = int( max_age_secs )
00246 
00247         if s_max_age_secs is not None:
00248             if str(s_max_age_secs).strip() == '':
00249                 s_max_age_secs = None
00250             else:
00251                 s_max_age_secs = int( s_max_age_secs )
00252 
00253         if pre_check is not None:
00254             if str(pre_check).strip() == '':
00255                 pre_check = None
00256             else:
00257                 pre_check = int(pre_check)
00258 
00259         if post_check is not None:
00260             if str(post_check).strip() == '':
00261                 post_check = None
00262             else:
00263                 post_check = int(post_check)
00264 
00265         self._policy_id = policy_id
00266         self._predicate = Expression( text=predicate )
00267         self._mtime_func = Expression( text=mtime_func )
00268         self._max_age_secs = max_age_secs
00269         self._s_max_age_secs = s_max_age_secs
00270         self._no_cache = int( no_cache )
00271         self._no_store = int( no_store )
00272         self._must_revalidate = int( must_revalidate )
00273         self._proxy_revalidate = int( proxy_revalidate )
00274         self._public = int( public )
00275         self._private = int( private )
00276         self._no_transform = int( no_transform )
00277         self._vary = vary
00278         self._etag_func = Expression( text=etag_func )
00279         self._enable_304s = int ( enable_304s )
00280         self._last_modified = int( last_modified )
00281         self._pre_check = pre_check
00282         self._post_check = post_check
00283 
00284     def getPolicyId( self ):
00285         """
00286         """
00287         return self._policy_id
00288 
00289     def getPredicate( self ):
00290         """
00291         """
00292         return self._predicate.text
00293 
00294     def getMTimeFunc( self ):
00295         """
00296         """
00297         return self._mtime_func.text
00298 
00299     def getMaxAgeSecs( self ):
00300         """
00301         """
00302         return self._max_age_secs
00303 
00304     def getSMaxAgeSecs( self ):
00305         """
00306         """
00307         return getattr(self, '_s_max_age_secs', None)
00308 
00309     def getNoCache( self ):
00310         """
00311         """
00312         return self._no_cache
00313 
00314     def getNoStore( self ):
00315         """
00316         """
00317         return self._no_store
00318 
00319     def getMustRevalidate( self ):
00320         """
00321         """
00322         return self._must_revalidate
00323 
00324     def getProxyRevalidate( self ):
00325         """
00326         """
00327         return getattr(self, '_proxy_revalidate', 0)
00328 
00329     def getPublic( self ):
00330         """
00331         """
00332         return getattr(self, '_public', 0)
00333 
00334     def getPrivate( self ):
00335         """
00336         """
00337         return getattr(self, '_private', 0)
00338 
00339     def getNoTransform( self ):
00340         """
00341         """
00342         return getattr(self, '_no_transform', 0)
00343 
00344     def getVary( self ):
00345         """
00346         """
00347         return getattr(self, '_vary', '')
00348 
00349     def getETagFunc( self ):
00350         """
00351         """
00352         etag_func_text = ''
00353         etag_func = getattr(self, '_etag_func', None)
00354 
00355         if etag_func is not None:
00356             etag_func_text = etag_func.text
00357 
00358         return etag_func_text
00359 
00360     def getEnable304s(self):
00361         """
00362         """
00363         return getattr(self, '_enable_304s', 0)
00364 
00365     def getLastModified(self):
00366         """Should we set the last modified header?"""
00367         return getattr(self, '_last_modified', 1)
00368 
00369     def getPreCheck(self):
00370         """
00371         """
00372         return getattr(self, '_pre_check', None)
00373 
00374     def getPostCheck(self):
00375         """
00376         """
00377         return getattr(self, '_post_check', None)
00378 
00379     def testPredicate(self, expr_context):
00380         """ Does this request match our predicate?"""
00381         return self._predicate(expr_context)
00382 
00383     def getHeaders( self, expr_context ):
00384         """
00385             Does this request match our predicate?  If so, return a
00386             sequence of caching headers as ( key, value ) tuples.
00387             Otherwise, return an empty sequence.
00388         """
00389         headers = []
00390 
00391         if self.testPredicate( expr_context ):
00392 
00393             if self.getLastModified():
00394                 mtime = self._mtime_func( expr_context )
00395                 if type( mtime ) is type( '' ):
00396                     mtime = DateTime( mtime )
00397                 if mtime is not None:
00398                     mtime_str = rfc1123_date(mtime.timeTime())
00399                     headers.append( ( 'Last-modified', mtime_str ) )
00400 
00401             control = []
00402 
00403             if self.getMaxAgeSecs() is not None:
00404                 now = expr_context.vars[ 'time' ]
00405                 exp_time_str = rfc1123_date(now.timeTime() + self._max_age_secs)
00406                 headers.append( ( 'Expires', exp_time_str ) )
00407                 control.append( 'max-age=%d' % self._max_age_secs )
00408 
00409             if self.getSMaxAgeSecs() is not None:
00410                 control.append( 's-maxage=%d' % self._s_max_age_secs )
00411 
00412             if self.getNoCache():
00413                 control.append( 'no-cache' )
00414                 # The following is for HTTP 1.0 clients
00415                 headers.append(('Pragma', 'no-cache'))
00416 
00417             if self.getNoStore():
00418                 control.append( 'no-store' )
00419 
00420             if self.getPublic():
00421                 control.append( 'public' )
00422 
00423             if self.getPrivate():
00424                 control.append( 'private' )
00425 
00426             if self.getMustRevalidate():
00427                 control.append( 'must-revalidate' )
00428 
00429             if self.getProxyRevalidate():
00430                 control.append( 'proxy-revalidate' )
00431 
00432             if self.getNoTransform():
00433                 control.append( 'no-transform' )
00434 
00435             pre_check = self.getPreCheck()
00436             if pre_check is not None:
00437                 control.append('pre-check=%d' % pre_check)
00438 
00439             post_check = self.getPostCheck()
00440             if post_check is not None:
00441                 control.append('post-check=%d' % post_check)
00442 
00443             if control:
00444                 headers.append( ( 'Cache-control', ', '.join( control ) ) )
00445 
00446             if self.getVary():
00447                 headers.append( ( 'Vary', self._vary ) )
00448 
00449             if self.getETagFunc():
00450                 headers.append( ( 'ETag', self._etag_func( expr_context ) ) )
00451 
00452         return headers
00453 
00454 
00455 
00456 class CachingPolicyManager( SimpleItem, CacheManager ):
00457     """
00458         Manage the set of CachingPolicy objects for the site;  dispatch
00459         to them from skin methods.
00460     """
00461 
00462     implements(ICachingPolicyManager)
00463     __implements__ = z2ICachingPolicyManager
00464 
00465     id = 'caching_policy_manager'
00466     meta_type = 'CMF Caching Policy Manager'
00467     _isCacheManager = 1 # Dead chicken. Yum.
00468 
00469     security = ClassSecurityInfo()
00470 
00471     def __init__( self ):
00472         self._policy_ids = ()
00473         self._policies = PersistentMapping()
00474 
00475     #
00476     #   ZMI
00477     #
00478     manage_options = ( ( { 'label'  : 'Policies'
00479                          , 'action' : 'manage_cachingPolicies'
00480                          , 'help'   : ('CMFCore', 'CPMPolicies.stx')
00481                          }
00482                        ,
00483                        )
00484                      + CacheManager.manage_options
00485                      + SimpleItem.manage_options
00486                      )
00487 
00488     security.declareProtected( ManagePortal, 'manage_cachingPolicies' )
00489     manage_cachingPolicies = DTMLFile( 'cachingPolicies', _dtmldir )
00490 
00491     security.declarePublic( 'listPolicies' )
00492     def listPolicies( self ):
00493         """List '(id, (policy, typeObjectName))' tuples for all policies.
00494         """
00495         return tuple([ (id, self._policies[id]) for id in self._policy_ids ])
00496 
00497     security.declareProtected( ManagePortal, 'addPolicy' )
00498     def addPolicy( self
00499                  , policy_id
00500                  , predicate           # TALES expr (def. 'python:1')
00501                  , mtime_func          # TALES expr (def. 'object/modified')
00502                  , max_age_secs        # integer, seconds (def. 0)
00503                  , no_cache            # boolean (def. 0)
00504                  , no_store            # boolean (def. 0)
00505                  , must_revalidate     # boolean (def. 0)
00506                  , vary                # string value
00507                  , etag_func           # TALES expr (def. '')
00508                  , REQUEST=None
00509                  , s_max_age_secs=None # integer, seconds (def. None)
00510                  , proxy_revalidate=0  # boolean (def. 0)
00511                  , public=0            # boolean (def. 0)
00512                  , private=0           # boolean (def. 0)
00513                  , no_transform=0      # boolean (def. 0)
00514                  , enable_304s=0       # boolean (def. 0)
00515                  , last_modified=1     # boolean (def. 1)
00516                  , pre_check=None      # integer, default None
00517                  , post_check=None     # integer, default None
00518                  ):
00519         """
00520             Add a caching policy.
00521         """
00522         if max_age_secs is None or str(max_age_secs).strip() == '':
00523             max_age_secs = None
00524         else:
00525             max_age_secs = int(max_age_secs)
00526 
00527         if s_max_age_secs is None or str(s_max_age_secs).strip() == '':
00528             s_max_age_secs = None
00529         else:
00530             s_max_age_secs = int(s_max_age_secs)
00531 
00532         if pre_check is None or str(pre_check).strip() == '':
00533             pre_check = None
00534         else:
00535             pre_check = int(pre_check)
00536 
00537         if post_check is None or str(post_check).strip() == '':
00538             post_check = None
00539         else:
00540             post_check = int(post_check)
00541 
00542         self._addPolicy( policy_id
00543                        , predicate
00544                        , mtime_func
00545                        , max_age_secs
00546                        , no_cache
00547                        , no_store
00548                        , must_revalidate
00549                        , vary
00550                        , etag_func
00551                        , s_max_age_secs
00552                        , proxy_revalidate
00553                        , public
00554                        , private
00555                        , no_transform
00556                        , enable_304s
00557                        , last_modified
00558                        , pre_check
00559                        , post_check
00560                        )
00561         if REQUEST is not None: 
00562             REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
00563                                           + '/manage_cachingPolicies'
00564                                           + '?manage_tabs_message='
00565                                           + 'Policy+added.'
00566                                           )
00567 
00568     security.declareProtected( ManagePortal, 'updatePolicy' )
00569     def updatePolicy( self
00570                     , policy_id
00571                     , predicate           # TALES expr (def. 'python:1')
00572                     , mtime_func          # TALES expr (def. 'object/modified')
00573                     , max_age_secs        # integer, seconds (def. 0)
00574                     , no_cache            # boolean (def. 0)
00575                     , no_store            # boolean (def. 0)
00576                     , must_revalidate     # boolean (def. 0)
00577                     , vary                # string value
00578                     , etag_func           # TALES expr (def. '')
00579                     , REQUEST=None
00580                     , s_max_age_secs=None # integer, seconds (def. 0)
00581                     , proxy_revalidate=0  # boolean (def. 0)
00582                     , public=0            # boolean (def. 0)
00583                     , private=0           # boolean (def. 0)
00584                     , no_transform=0      # boolean (def. 0)
00585                     , enable_304s=0       # boolean (def. 0)
00586                     , last_modified=1     # boolean (def. 1)
00587                     , pre_check=0         # integer, default=None
00588                     , post_check=0        # integer, default=None
00589                     ):
00590         """
00591             Update a caching policy.
00592         """
00593         if max_age_secs is None or str(max_age_secs).strip() == '':
00594             max_age_secs = None
00595         else:
00596             max_age_secs = int(max_age_secs)
00597 
00598         if s_max_age_secs is None or str(s_max_age_secs).strip() == '':
00599             s_max_age_secs = None
00600         else:
00601             s_max_age_secs = int(s_max_age_secs)
00602 
00603         if pre_check is None or str(pre_check).strip() == '':
00604             pre_check = None
00605         else:
00606             pre_check = int(pre_check)
00607 
00608         if post_check is None or str(post_check).strip() == '':
00609             post_check = None
00610         else:
00611             post_check = int(post_check)
00612 
00613         self._updatePolicy( policy_id
00614                           , predicate
00615                           , mtime_func
00616                           , max_age_secs
00617                           , no_cache
00618                           , no_store
00619                           , must_revalidate
00620                           , vary
00621                           , etag_func
00622                           , s_max_age_secs
00623                           , proxy_revalidate
00624                           , public
00625                           , private
00626                           , no_transform
00627                           , enable_304s
00628                           , last_modified
00629                           , pre_check
00630                           , post_check
00631                           )
00632         if REQUEST is not None: 
00633             REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
00634                                           + '/manage_cachingPolicies'
00635                                           + '?manage_tabs_message='
00636                                           + 'Policy+updated.'
00637                                           )
00638 
00639     security.declareProtected( ManagePortal, 'movePolicyUp' )
00640     def movePolicyUp( self, policy_id, REQUEST=None ):
00641         """
00642             Move a caching policy up in the list.
00643         """
00644         policy_ids = list( self._policy_ids )
00645         ndx = policy_ids.index( policy_id )
00646         if ndx == 0:
00647             msg = "Policy+already+first."
00648         else:
00649             self._reorderPolicy( policy_id, ndx - 1 )
00650             msg = "Policy+moved."
00651         if REQUEST is not None:
00652             REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
00653                               + '/manage_cachingPolicies'
00654                               + '?manage_tabs_message=%s' % msg
00655                               )
00656 
00657     security.declareProtected( ManagePortal, 'movePolicyDown' )
00658     def movePolicyDown( self, policy_id, REQUEST=None ):
00659         """
00660             Move a caching policy down in the list.
00661         """
00662         policy_ids = list( self._policy_ids )
00663         ndx = policy_ids.index( policy_id )
00664         if ndx == len( policy_ids ) - 1:
00665             msg = "Policy+already+last."
00666         else:
00667             self._reorderPolicy( policy_id, ndx + 1 )
00668             msg = "Policy+moved."
00669         if REQUEST is not None:
00670             REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
00671                               + '/manage_cachingPolicies'
00672                               + '?manage_tabs_message=%s' % msg
00673                               )
00674 
00675     security.declareProtected( ManagePortal, 'removePolicy' )
00676     def removePolicy( self, policy_id, REQUEST=None ):
00677         """
00678             Remove a caching policy.
00679         """
00680         self._removePolicy( policy_id )
00681         if REQUEST is not None:
00682             REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
00683                               + '/manage_cachingPolicies'
00684                               + '?manage_tabs_message=Policy+removed.'
00685                               )
00686 
00687     #
00688     #   Policy manipulation methods.
00689     #
00690     security.declarePrivate( '_addPolicy' )
00691     def _addPolicy( self
00692                   , policy_id
00693                   , predicate
00694                   , mtime_func
00695                   , max_age_secs
00696                   , no_cache
00697                   , no_store
00698                   , must_revalidate
00699                   , vary
00700                   , etag_func
00701                   , s_max_age_secs=None
00702                   , proxy_revalidate=0
00703                   , public=0
00704                   , private=0
00705                   , no_transform=0
00706                   , enable_304s=0
00707                   , last_modified=1
00708                   , pre_check=None
00709                   , post_check=None
00710                   ):
00711         """
00712             Add a policy to our registry.
00713         """
00714         policy_id = str( policy_id ).strip()
00715 
00716         if not policy_id:
00717             raise ValueError, "Policy ID is required!"
00718 
00719         if policy_id in self._policy_ids:
00720             raise KeyError, "Policy %s already exists!" % policy_id
00721 
00722         self._policies[ policy_id ] = CachingPolicy( policy_id
00723                                                    , predicate
00724                                                    , mtime_func
00725                                                    , max_age_secs
00726                                                    , no_cache
00727                                                    , no_store
00728                                                    , must_revalidate
00729                                                    , vary
00730                                                    , etag_func
00731                                                    , s_max_age_secs
00732                                                    , proxy_revalidate
00733                                                    , public
00734                                                    , private
00735                                                    , no_transform
00736                                                    , enable_304s
00737                                                    , last_modified
00738                                                    , pre_check
00739                                                    , post_check
00740                                                    )
00741         idlist = list( self._policy_ids )
00742         idlist.append( policy_id )
00743         self._policy_ids = tuple( idlist )
00744 
00745     security.declarePrivate( '_updatePolicy' )
00746     def _updatePolicy( self
00747                      , policy_id
00748                      , predicate
00749                      , mtime_func
00750                      , max_age_secs
00751                      , no_cache
00752                      , no_store
00753                      , must_revalidate
00754                      , vary
00755                      , etag_func
00756                      , s_max_age_secs=None
00757                      , proxy_revalidate=0
00758                      , public=0
00759                      , private=0
00760                      , no_transform=0
00761                      , enable_304s=0
00762                      , last_modified=1
00763                      , pre_check=None
00764                      , post_check=None
00765                      ):
00766         """
00767             Update a policy in our registry.
00768         """
00769         if policy_id not in self._policy_ids:
00770             raise KeyError, "Policy %s does not exist!" % policy_id
00771 
00772         self._policies[ policy_id ] = CachingPolicy( policy_id
00773                                                    , predicate
00774                                                    , mtime_func
00775                                                    , max_age_secs
00776                                                    , no_cache
00777                                                    , no_store
00778                                                    , must_revalidate
00779                                                    , vary
00780                                                    , etag_func
00781                                                    , s_max_age_secs
00782                                                    , proxy_revalidate
00783                                                    , public
00784                                                    , private
00785                                                    , no_transform
00786                                                    , enable_304s
00787                                                    , last_modified
00788                                                    , pre_check
00789                                                    , post_check
00790                                                    )
00791 
00792     security.declarePrivate( '_reorderPolicy' )
00793     def _reorderPolicy( self, policy_id, newIndex ):
00794         """
00795             Reorder a policy in our registry.
00796         """
00797         if policy_id not in self._policy_ids:
00798             raise KeyError, "Policy %s does not exist!" % policy_id
00799 
00800         idlist = list( self._policy_ids )
00801         ndx = idlist.index( policy_id )
00802         pred = idlist[ ndx ]
00803         idlist = idlist[ :ndx ] + idlist[ ndx+1: ]
00804         idlist.insert( newIndex, pred )
00805         self._policy_ids = tuple( idlist )
00806 
00807     security.declarePrivate( '_removePolicy' )
00808     def _removePolicy( self, policy_id ):
00809         """
00810             Remove a policy from our registry.
00811         """
00812         if policy_id not in self._policy_ids:
00813             raise KeyError, "Policy %s does not exist!" % policy_id
00814 
00815         del self._policies[ policy_id ]
00816         idlist = list( self._policy_ids )
00817         ndx = idlist.index( policy_id )
00818         idlist = idlist[ :ndx ] + idlist[ ndx+1: ]
00819         self._policy_ids = tuple( idlist )
00820 
00821 
00822     #
00823     #   'portal_caching' interface methods
00824     #
00825     security.declareProtected( View, 'getHTTPCachingHeaders' )
00826     def getHTTPCachingHeaders( self, content, view_method, keywords, time=None):
00827         """
00828             Return a list of HTTP caching headers based on 'content',
00829             'view_method', and 'keywords'.
00830         """
00831         # XXX: this method violates the rules for tools/utilities:
00832         # createCPContext depends on a non-utility tool
00833         context = createCPContext( content, view_method, keywords, time=time )
00834         for policy_id, policy in self.listPolicies():
00835 
00836             headers = policy.getHeaders( context )
00837             if headers:
00838                 return headers
00839 
00840         return ()
00841 
00842     security.declareProtected( View, 'getModTimeAndETag' )
00843     def getModTimeAndETag( self, content, view_method, keywords, time=None):
00844         """ Return the modification time and ETag for the content object,
00845             view method, and keywords as the tuple (modification_time, etag,
00846             set_last_modified_header), where modification_time is a DateTime,
00847             or None.
00848         """
00849         # XXX: this method violates the rules for tools/utilities:
00850         # createCPContext depends on a non-utility tool
00851         context = createCPContext( content, view_method, keywords, time=time )
00852         for policy_id, policy in self.listPolicies():
00853             if policy.getEnable304s() and policy.testPredicate(context):
00854 
00855                 last_modified = policy._mtime_func(context)
00856                 if type(last_modified) is type(''):
00857                     last_modified = DateTime(last_modified)
00858 
00859                 content_etag = None
00860                 if policy.getETagFunc():
00861                     content_etag = policy._etag_func(context)
00862 
00863                 return (last_modified, content_etag, policy.getLastModified())
00864 
00865         return None
00866 
00867     #
00868     # OFS.CacheManager API
00869     #
00870     security.declarePrivate('ZCacheManager_getCache')
00871     def ZCacheManager_getCache(self):
00872         """ Retrieve a cache object
00873         """
00874         cache = getattr(self, '_cache', None)
00875 
00876         if cache is None:
00877             self._cache = CPMCache()
00878             cache = self._cache
00879 
00880         return cache
00881 
00882 
00883 InitializeClass( CachingPolicyManager )
00884 registerToolInterface('caching_policy_manager', ICachingPolicyManager)
00885 
00886 
00887 def handleCachingPolicyManagerEvent(ob, event):
00888     """ Event subscriber for (un)registering a CPM as CacheManager
00889     """
00890     if not ICachingPolicyManager.providedBy(ob):
00891         return
00892 
00893     if IObjectMovedEvent.providedBy(event):
00894         if event.newParent is not None:
00895             ids = getVerifiedManagerIds(event.newParent)
00896             id = ob.getId()
00897             if id not in ids:
00898                 setattr(event.newParent, ZCM_MANAGERS, ids + (id,))
00899 
00900     elif IObjectWillBeMovedEvent.providedBy(event):
00901         if event.oldParent is not None:
00902             ids = list(getVerifiedManagerIds(event.oldParent))
00903             id = ob.getId()
00904             if id in ids:
00905                 ids.remove(id)
00906                 setattr(event.oldParent, ZCM_MANAGERS, tuple(ids))
00907 
00908 
00909 def manage_addCachingPolicyManager( self, REQUEST=None ):
00910     """
00911         Add a CPM to self.
00912     """
00913     id = CachingPolicyManager.id
00914     mgr = CachingPolicyManager()
00915     self._setObject( id, mgr )
00916 
00917     if REQUEST is not None:
00918         REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
00919                       + '/manage_main'
00920                       + '?manage_tabs_message=Caching+Policy+Manager+added.'
00921                       )