""" $RCSfile: CacheManager.py,v $ ZopeXMLMethods provides filters to apply to Zope objects for XML/XSLT processing. XSLTMethod associates XSLT transformers with XML documents. ZopeXMLMethods includes XML Method Cache Manager that is specialized to notice changes to the XML source files and to store cached contents in files in the filesystem, rather than the Zope object database. Author: Craeg Strong Modified by Philipp von Weitershausen $Id: CacheManager.py,v 1.22 2003/03/30 03:45:47 cstrong Exp $ """ import os, re, sys, urlparse # base classes from OFS.SimpleItem import SimpleItem from Products.ZCatalog.CatalogAwareness import CatalogAware from OFS.PropertyManager import PropertyManager # peer classes from ICacheManager import ICacheManager import XSLTMethod # the module # Zope builtins import OFS import Globals from Globals import Acquisition, MessageDialog, HTML from Acquisition import aq_chain, aq_parent from AccessControl import ClassSecurityInfo from Products.PageTemplates.PageTemplateFile import PageTemplateFile # Permission strings # By defining these constants, no new permissions will be created when # misspelling them in the declareProtected() call PERM_EDIT = "Edit" PERM_MANAGE = "Manage XML Method Cache" ################################################################ # Defaults ################################################################ cachePrefix = 'ZopeXMLCache' def defaultCachePrefix(): spoolDirectory = "/tmp" if sys.platform == 'win32': spoolDirectory = r"c:\tmp" return os.path.join(spoolDirectory, cachePrefix) ################################################################ # Module Scoped Convenience Methods ################################################################ def addInstance(folder, id, title='', description=''): """ This is a convenience factory method for creating an instance of CacheManager. It returns the object created, and may therefore be more convenient than the addCacheManager() method it calls. It is used by the unit testing programs. """ folder.manage_addProduct['ZopeXMLMethods'].addXMLMethodCacheManager(id,title,description) return folder[id] ################################################################ # Container Methods ################################################################ manage_addXMLMethodCacheManagerForm = PageTemplateFile('www/create_cachemgr.pt', globals()) def manage_addXMLMethodCacheManager(self, id, title='', description='', REQUEST=None, RESPONSE=None): """ Add an XML Method Cache Manager to a folder. Called from the create_cachemgr.dtml GUI form in the Zope Management interface. It calls addXMLMethodCacheManager to actually do the work. """ try: self.addXMLMethodCacheManager(id, title, description) RESPONSE.redirect(self.absolute_url() + "/manage_main") except Exception, e: message = str(e) mesage.replace('\n','
') return MessageDialog(title = 'Error', message = message, action = 'manage_main') def addXMLMethodCacheManager(self, id, title='', description=''): """ Add an XML Method Cache Manager to a folder. You should call this method directly if you are creating an instance of CacheManager programmatically. """ if not id: raise Exception('Required fields must not be blank') self._setObject(id, CacheManager(id, title, description)) ################################################################ # CacheManager class ################################################################ class CacheManager(CatalogAware, # ZCatalog support PropertyManager, # Property support SimpleItem): """ CacheManager caches the results of performing transformations with filters. It does not inherit from OFS/CacheManager because it has so little in common with it. @@ revisit this later CKS 3/2/2003 """ meta_type = 'XML Method Cache Manager' __implements__ = ICacheManager _properties = ( {'id':'title', 'type':'string', 'mode': 'w'}, {'id':'description', 'type':'string', 'mode': 'w'}, {'id':'cachePrefix', 'type':'string', 'mode': 'w'}, ) manage_options = ( { 'label':'Cache', 'action':'manage_cacheForm', 'help':('ZopeXMLMethods', 'cache.stx') }, ) + OFS.PropertyManager.PropertyManager.manage_options \ + OFS.SimpleItem.SimpleItem.manage_options _security = ClassSecurityInfo() _security.declareProtected(PERM_MANAGE, 'manage_cacheForm') manage_cacheForm = PageTemplateFile('www/edit_cachemgr.pt', globals()) def __init__(self, id, title, description): """ Initialize a new instance of CacheManager """ self.id = id self.title = title self.description = description self.cachePrefix = defaultCachePrefix() self.cacheFiles = {} ################################################################ # Methods called from ZMI pages ################################################################ _security.declareProtected(PERM_EDIT, 'manage_setCachingOn') def manage_setCachingOn(self, REQUEST): """ ZMI method: turn caching on for all XML Method objects. """ message = self.batchSetCachingOn(REQUEST) message.replace('\n','
') return self.manage_cacheForm(batchOperationOutput = message) _security.declareProtected(PERM_EDIT, 'manage_setCachingOff') def manage_setCachingOff(self, REQUEST): """ ZMI method: turn caching off for all XML Method objects. """ message = self.batchSetCachingOff(REQUEST) message.replace('\n','
') return self.manage_cacheForm(batchOperationOutput = message) _security.declareProtected(PERM_EDIT, 'manage_clearCache') def manage_clearCache(self): """ ZMI method: clear cache """ message = self.clearCache() message.replace('\n','
') return self.manage_cacheForm(batchOperationOutput = message) _security.declareProtected(PERM_EDIT, 'manage_listCacheFiles') def manage_listCacheFiles(self): """ ZMI method: list cache files """ message = "Cache Files:\n\n" list = self.listCacheFiles() if list: for file in list: message = message + file + '\n' else: message = 'No files in cache' message.replace('\n','
') return self.manage_cacheForm(batchOperationOutput = message) _security.declareProtected(PERM_EDIT, 'manage_editProperties') def manage_editProperties(self, REQUEST): """ Cover for PropertyManager.manage_editProperties() method. First validate cachePrefix, then pass it on to the inherited method for further processing """ for prop in self._propertyMap(): name = prop['id'] if name == 'cachePrefix': value = REQUEST.get(name, '') if not self.isValidCachePrefix(value): return MessageDialog(title = 'ERROR', message = value + ' is not a valid Cache Prefix', action = 'manage_propertiesForm') return CacheManager.inheritedAttribute("manage_editProperties")(self, REQUEST) ################################################################ # Methods implementing the ICacheManager interface ################################################################ _security.declareProtected(PERM_EDIT, 'batchSetCachingOn') def batchSetCachingOn(self, REQUEST): """ Turns caching on for all instances of all types of XML Methods within the scope of this cache manager. """ parent = self.getParentFolder() message = "Turning Caching On for all XML filter instances under " + \ parent.getId() + \ " Folder:\n" message = message + \ self.batchExecute(parent, 'setCachingOn', REQUEST) return message _security.declareProtected(PERM_EDIT, 'batchSetCachingOff') def batchSetCachingOff(self, REQUEST): """ Turns caching off for all instances of all types of XML Methods within the scope of this cache manager. """ parent = self.getParentFolder() message = "Turning Caching Off for all XML filter instances under " + \ parent.getId() + \ " Folder:\n" message = message + \ self.batchExecute(parent, 'setCachingOff', REQUEST) return message _security.declareProtected(PERM_EDIT, 'cacheFileTimeStamp') def cacheFileTimeStamp(self, url): """ Return the last modified time of the cache file for the passed in filter client object, or 0 if the cache file does not exist """ fileName = self.cacheFileName(url) cachetime = 0 if os.path.exists(fileName): return os.path.getmtime(fileName) else: return 0 _security.declareProtected(PERM_EDIT, 'valueFromCache') def valueFromCache(self, url): """ Retrieve the output from the cache for the passed in filter client object, or None if the cache file does not exist. """ fileName = self.cacheFileName(url) cache = open(fileName, 'rb') result = cache.read() cache.close() return result _security.declareProtected(PERM_EDIT, 'saveToCache') def saveToCache(self, url, contents): """ Save the contents to the cache for the passed in filter client object. It will overwrite the previous contents of the cache file, or create a new cache file if it does not yet exist. """ fileName = self.cacheFileName(url) cache = open(fileName, 'wb') cache.write(contents) cache.close() self.cacheFiles[url] = fileName self._p_changed = 1 _security.declareProtected(PERM_EDIT, 'clearCache') def clearCache(self): """ Clear the cache. This involves removing every cache file from the file system. """ message = "" for fileName in self.cacheFiles.values(): try: os.remove(fileName) message = message + fileName + " successfully removed\n" except Exception, value: message = message + "%s not removed, error: %s\n" % (fileName, value) self.cacheFiles.clear() return message or "No files in cache" _security.declareProtected(PERM_EDIT, 'listCacheFiles') def listCacheFiles(self): """ Return the list of files in the cache """ return self.cacheFiles.values() ################################################################ # Utilities ################################################################ def isValidCachePrefix(self, value): """ Return 1 if this represents a valid value for a cache prefix """ dirname = os.path.dirname(value) # We would like to check write permission in the directory # also, but this is difficult to do portably... TBD return os.path.exists(dirname) def cacheFileName(self, url): """ Algorithmically determine the leaf name of the file in which to store the transformed contents of the passed in XML filter client object. """ (addressingScheme, networkLocation, path, params, query, fragment) = \ urlparse.urlparse(url) return os.path.normpath(self.cachePrefix + re.sub('[/\\\]','_',path)) def getParentFolder(self): """ Returns the folder containing this instance of CacheManager """ return aq_parent(self) def batchExecute(self, context, methodName, REQUEST): """ Recursively execute passed-in method on all objects of type XSLTMethod within the passed-in context (ie Zope folder). Returns a list of the object IDs of the affected objects. """ metaType = 'XSLT Method' # @@ expand to more types later CKS 3/23/2003 objects = context.ZopeFind(context, obj_metatypes=[metaType], search_sub=1) func = XSLTMethod.XSLTMethod.__dict__[methodName] message = "\n\n" for doc in objects: try: message = message + "\nFound XSLTMethod " + \ doc[0] func(doc[1], REQUEST) message = message + "...success" except Exception, value: mesage = message + "...error: %s" % (value) return message # innocuous methods should be declared public _security.declarePublic('getSelf') def getSelf(self): "Return this object. For use in DTML scripts" return self.aq_chain[0] ################################################################ # Support for Schema Migration ################################################################ def repair(self): """ Repair this object. This method is used for schema migration, when the class definition changes and existing instances of previous versions must be updated. """ # Nothing to repair at the moment # register security information Globals.InitializeClass(CacheManager)