From 7d209b4b22b28b0b128384f22efbd49779ca8a09 Mon Sep 17 00:00:00 2001 From: zmsdev <33760154+zmsdev@users.noreply.github.com> Date: Wed, 24 Jun 2026 14:10:24 +0200 Subject: [PATCH 1/6] Shared cache; RAM Cache or Mem Cache --- Products/zms/ZMSMetaobjManager.py | 66 ++++++++--------- Products/zms/_cachemanager.py | 113 ++++++++++++++++++++++++++---- Products/zms/zms.py | 28 +++----- 3 files changed, 143 insertions(+), 64 deletions(-) diff --git a/Products/zms/ZMSMetaobjManager.py b/Products/zms/ZMSMetaobjManager.py index 314bacba9..9dd959103 100644 --- a/Products/zms/ZMSMetaobjManager.py +++ b/Products/zms/ZMSMetaobjManager.py @@ -508,7 +508,34 @@ def renderTemplate(self, obj): obj.clear_request_context(obj.REQUEST, prefix) return v - + def __aq_metaobjs__(self, aqs=[]): + """ + Resolve acquired meta-object entries from portal master. + """ + aq_obs = {} + if not aqs: return aq_obs + unresolved = [] + for aq in aqs: + subobjects = aq.get('subobjects') + ob = self.model.get(aq['id']) + if ob and not ob.get('acquired') == 1: + aq_ob = {'acquired': 1, 'subobjects': subobjects, **ob} + aq_obs[id] = aq_ob + if aq_ob.get('type') == 'ZMSPackage' and subobjects == 1: + package = aq_ob['id'] + for sub_id, sub_ob in self.model.items(): + if sub_ob.get('package') == package: + aq_sub_ob = {'acquired': 1, **sub_ob} + aq_obs[sub_id] = aq_sub_ob + else: + unresolved.append(aq) + if unresolved: + portalMaster = self.getPortalMaster() + if portalMaster: + # merge, portal master first to allow local override + aq_obs = {**portalMaster.metaobj_manager.__aq_metaobjs__(unresolved), **aq_obs} + return aq_obs + def __get_metaobjs__(self): """ Return all meta-objects, including acquired entries from portal master. @@ -523,36 +550,11 @@ def __get_metaobjs__(self): try: return self.fetchReqBuff(reqBuffId) except: pass # Get value. - obs = {} - m = self.model - aq_obs = None - for id in m: - ob = m[id] - # handle acquisition - if ob.get('acquired', 0) == 1: - acquired = 1 - subobjects = ob.get('subobjects', 1) - if aq_obs is None: - portalMaster = self.getPortalMaster() - if portalMaster is not None: - aq_obs = portalMaster.metaobj_manager.__get_metaobjs__() - if aq_obs is not None: - if id in aq_obs: - ob = aq_obs[id].copy() - else: - ob = {'id':id,'type':'ZMSUnknown'} - ob['acquired'] = acquired - ob['subobjects'] = subobjects - obs[id] = ob - if ob['type'] == 'ZMSPackage' and ob['subobjects'] == 1: - for aq_id in aq_obs: - ob = aq_obs[aq_id].copy() - if ob.get( 'package') == id: - ob['acquired'] = 1 - obs[aq_id] = ob - else: - obs[id] = ob - return self.storeReqBuff( reqBuffId, obs) + own_obs = {id: ob for id, ob in self.model.items() if not ob.get('acquired') == 1} + aq_obs = [ob for ob in self.model.values() if ob.get('acquired') == 1] + master_obs = self.__aq_metaobjs__(aq_obs) + total_obs = {**own_obs, **master_obs} + return self.storeReqBuff( reqBuffId, total_obs) def __get_metaobj__(self, id): @@ -620,7 +622,7 @@ def getMetaobjIds(self, sort=None, excl_ids=[]): if sort == True: ids = sorted(ids,key=lambda x:self.display_type(meta_id=x)) elif sort == False: - ids = sorted(ids,key=lambda x:obs[x].get('name',x)) + ids = sorted(ids,key=lambda x:obs.get(x, {}).get('name', x)) return ids diff --git a/Products/zms/_cachemanager.py b/Products/zms/_cachemanager.py index 2b5f6dd4a..d76baf74b 100644 --- a/Products/zms/_cachemanager.py +++ b/Products/zms/_cachemanager.py @@ -27,10 +27,72 @@ from Products.zms import standard from zope.globalrequest import getRequest +# Kill switch for shared cache usage. Set to False to disable shared caching. +shared_cache_enabled = True + +# ID of the shared cache. +# Valid Neta-Types are 'RAM Cache Manager' and 'MemCache Cache Manager'. +shared_cache_id = 'shared_cache' + +# Keys that are shared across requests and should be stored in the shared cache. +# This list can be extended with additional keys as needed. +shared_keys = ['ZMSMetaobjManager.__get_metaobjs__'] + +# Class that wraps an object to provide the ZCacheable interface for shared caching. +class SharedCacheable: + def __init__(self, ob): + self._ob = ob + + def ZCacheable_getModTime(self, *args, **kwargs): + return 0 + + def ZCacheable_getIdentifier(self): + return "/".join(self._ob.getPhysicalPath()) + + def ZCacheable_isCachingEnabled(self): + return True + + def getPhysicalPath(self): + return self._ob.getPhysicalPath() + +# Helper function to retrieve the shared cache manager if available. +def get_cache(self): + cache = None + if shared_cache_enabled and hasattr(self, shared_cache_id): + shared_cache = getattr(self, shared_cache_id) + cache = shared_cache.ZCacheManager_getCache() + return cache + + +# Request-local buffer management. +buff_key = '__buff__' + +# Lightweight attribute container used for request-local buffering. class Buff(object): """Lightweight attribute container used for request-local buffering.""" pass +# Helper functions to get and set the request-local buffer. +def get_buff(request): + buff = getattr(request, buff_key, None) + if buff is None: + buff = Buff() + set_buff(request, buff) + return buff + +# Helper function to set the request-local buffer. +def set_buff(request, buff): + setattr(request, buff_key, buff) + + +# Helper function to retrieve the current request object, either from the instance or globally. +def get_request(self): + request = getattr(self, 'REQUEST', None) + if request: + return request + return getRequest() + +# Request-scoped buffer helpers for expensive values computed during one request. class ReqBuff(object): """Request-scoped buffer helpers for expensive values computed during one request.""" @@ -44,7 +106,8 @@ def getReqBuffId(self, key): @return: Namespaced buffer key. @rtype: C{str} """ - return '%s_%s'%('_'.join(self.getPhysicalPath()[2:]), key) + path = self.getPhysicalPath() + return f"{hash(path)}_{key}" def clearReqBuff(self, prefix='', REQUEST=None): @@ -56,15 +119,16 @@ def clearReqBuff(self, prefix='', REQUEST=None): @param REQUEST: Optional request object. @type REQUEST: C{object} """ - request = getattr(self, 'REQUEST', getRequest()) - buff = request.get('__buff__', Buff()) reqBuffId = self.getReqBuffId(prefix) + request = get_request(self) + buff = get_buff(request) if len(prefix) > 0: reqBuffId += '.' for key in list(buff.__dict__): if key.startswith(reqBuffId): delattr(buff, key) - + set_buff(request, buff) + def fetchReqBuff(self, key=None, REQUEST=None): """ @@ -75,11 +139,26 @@ def fetchReqBuff(self, key=None, REQUEST=None): @return: The buffered value. @rtype: C{object} """ - request = getattr(self, 'REQUEST', getRequest()) - if key is None: # For debugging purposes, return whole buffer. - return None # request.get('__buff__',{}) - buff = request['__buff__'] reqBuffId = self.getReqBuffId(key) + request = get_request(self) + buff = get_buff(request) + if not hasattr(buff, reqBuffId): + # RAM cache is optional, so we ignore errors if it's not available. + try: + if key in shared_cache_keys: + cache = get_cache(self) + if cache: + cacheable = SharedCacheable(self) + # Note: keywords/view_name can be used for namespacing if needed. + value = cache.ZCache_get(cacheable, view_name='shared', keywords={'key': key}) + if value: + # Store the value in the request buffer for future access. + setattr(buff, reqBuffId, value) + set_buff(request, buff) + return value + except Exception as e: + print("SharedCache not available:", key, e) + pass return getattr(buff, reqBuffId) @@ -96,11 +175,19 @@ def storeReqBuff(self, key, value, REQUEST=None): @return: The value that was stored. @rtype: C{object} """ - request = getattr(self, 'REQUEST', getRequest()) - buff = request.get('__buff__', None) - if buff is None: - buff = Buff() reqBuffId = self.getReqBuffId(key) + request = get_request(self) + buff = get_buff(request) setattr(buff, reqBuffId, value) - request.set('__buff__', buff) + set_buff(request, buff) + # RAM cache is optional, so we ignore errors if it's not available. + try: + if key in shared_cache_keys: + cache = get_cache(self) + if cache: + cacheable = SharedCacheable(self) + cache.ZCache_set(cacheable, value, view_name='shared', keywords={'key': key}) + except Exception as e: + print("SharedCache not available:", key, e) + pass return value diff --git a/Products/zms/zms.py b/Products/zms/zms.py index c1153ac63..8d42e1906 100644 --- a/Products/zms/zms.py +++ b/Products/zms/zms.py @@ -580,6 +580,7 @@ def getDocumentElement(self): def getRootElement(self): """Return the topmost portal master in the current site hierarchy.""" + #print('[DEBUG] getRootElement', self.getPhysicalPath()) doc_elmnt = self while True: portal_mstr = doc_elmnt.getPortalMaster() @@ -590,28 +591,18 @@ def getRootElement(self): def getAbsoluteHome(self): """Return the home folder of the portal master or the local site.""" - portalMaster = self.getPortalMaster() - if portalMaster: - return portalMaster.getAbsoluteHome() - return self.getHome() + #print('[DEBUG] getAbsoluteHome', self.getPhysicalPath()) + return self.getRootElement().getHome() def getHome(self): """Return the folder that contains the document element.""" - docElmnt = self.getDocumentElement() - ob = docElmnt + #print('[DEBUG] getHome', self.getPhysicalPath()) try: - depth = 0 - while ob.meta_type != 'Folder': - if depth > sys.getrecursionlimit(): - raise zExceptions.InternalError('Maximum recursion depth exceeded') - depth = depth + 1 - ob = ob.aq_parent + path = self.getPhysicalPath()[::-1] + home_id = path[path.index('content')+1] + return getattr(self, home_id) except: - try: - ob = getattr( docElmnt, docElmnt.absolute_url().split( '/')[-2]) - except: - ob = docElmnt.aq_parent - return ob + return self.aq_parent def getTrashcan(self): """Return the site's trashcan object.""" @@ -656,10 +647,9 @@ def getPortalClients(self): docElmnts = [] v = self.get_conf_properties().get('Portal.Clients', []) if v: - home = self.getHome() for id in v: try: - docElmnts.append(getattr(home, id).content) + docElmnts.append(getattr(self, id).content) except: pass return docElmnts From 74e988cabd26d2c0648f6515080dc2c404b67f86 Mon Sep 17 00:00:00 2001 From: drfho Date: Sun, 5 Jul 2026 17:01:05 +0200 Subject: [PATCH 2/6] @zmsdev: fixed var name --- Products/zms/_cachemanager.py | 4 ++-- Products/zms/version.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Products/zms/_cachemanager.py b/Products/zms/_cachemanager.py index d76baf74b..20f2355d3 100644 --- a/Products/zms/_cachemanager.py +++ b/Products/zms/_cachemanager.py @@ -145,7 +145,7 @@ def fetchReqBuff(self, key=None, REQUEST=None): if not hasattr(buff, reqBuffId): # RAM cache is optional, so we ignore errors if it's not available. try: - if key in shared_cache_keys: + if key in shared_keys: cache = get_cache(self) if cache: cacheable = SharedCacheable(self) @@ -182,7 +182,7 @@ def storeReqBuff(self, key, value, REQUEST=None): set_buff(request, buff) # RAM cache is optional, so we ignore errors if it's not available. try: - if key in shared_cache_keys: + if key in shared_keys: cache = get_cache(self) if cache: cacheable = SharedCacheable(self) diff --git a/Products/zms/version.txt b/Products/zms/version.txt index 862ed00b6..fac066673 100644 --- a/Products/zms/version.txt +++ b/Products/zms/version.txt @@ -1 +1 @@ -6.1.0+12a850d +6.1.0+7d209b4 From 2968c8ea4adc4e5f46cb8653f7a43392f59e5478 Mon Sep 17 00:00:00 2001 From: zmsdev <33760154+zmsdev@users.noreply.github.com> Date: Wed, 8 Jul 2026 18:03:13 +0200 Subject: [PATCH 3/6] dump shared cache --- Products/zms/_cachemanager.py | 36 +++++++++++-------- .../manage_test_perf_multisite.py | 9 +++++ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Products/zms/_cachemanager.py b/Products/zms/_cachemanager.py index 20f2355d3..b79757365 100644 --- a/Products/zms/_cachemanager.py +++ b/Products/zms/_cachemanager.py @@ -130,6 +130,21 @@ def clearReqBuff(self, prefix='', REQUEST=None): set_buff(request, buff) + def fetchSharedCache(self, key): + # RAM cache is optional, so we ignore errors if it's not available. + try: + if key in shared_keys: + cache = get_cache(self) + if cache: + cacheable = SharedCacheable(self) + # Note: keywords/view_name can be used for namespacing if needed. + return cache.ZCache_get(cacheable, view_name='shared', keywords={'key': key}) + except Exception as e: + print("SharedCache not available:", key, e) + pass + return None + + def fetchReqBuff(self, key=None, REQUEST=None): """ Fetch one buffered value from the current request (raises if missing). @@ -144,21 +159,12 @@ def fetchReqBuff(self, key=None, REQUEST=None): buff = get_buff(request) if not hasattr(buff, reqBuffId): # RAM cache is optional, so we ignore errors if it's not available. - try: - if key in shared_keys: - cache = get_cache(self) - if cache: - cacheable = SharedCacheable(self) - # Note: keywords/view_name can be used for namespacing if needed. - value = cache.ZCache_get(cacheable, view_name='shared', keywords={'key': key}) - if value: - # Store the value in the request buffer for future access. - setattr(buff, reqBuffId, value) - set_buff(request, buff) - return value - except Exception as e: - print("SharedCache not available:", key, e) - pass + value = self.fetchSharedCache(key) + if value: + # Store the value in the request buffer for future access. + setattr(buff, reqBuffId, value) + set_buff(request, buff) + return value return getattr(buff, reqBuffId) diff --git a/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/manage_test_perf_multisite.py b/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/manage_test_perf_multisite.py index c3ae986d2..f2abf51a5 100644 --- a/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/manage_test_perf_multisite.py +++ b/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/manage_test_perf_multisite.py @@ -1,5 +1,11 @@ +from Products.zms import standard + def manage_test_perf_multisite( self): request = self.REQUEST + if request.get('shared_cache_id','') == 'shared_cache': + value = self.fetchSharedCache('ZMSMetaobjManager.__get_metaobjs__') + request.RESPONSE.setHeader('Content-Type','application/json; charset=utf-8') + return standard.str_json({'status':'ok','message':'Shared Cache is enabled.','value':value}) prt = [] prt.append('') prt.append('') @@ -67,6 +73,9 @@ def manage_test_perf_multisite( self): + + Shared Cache Test + """) From 546503351fb9972c408bc889c22dac53e3ca1c40 Mon Sep 17 00:00:00 2001 From: zmsdev <33760154+zmsdev@users.noreply.github.com> Date: Wed, 8 Jul 2026 18:19:13 +0200 Subject: [PATCH 4/6] manage_main --- .../manage_test_perf_multisite/manage_test_perf_multisite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/manage_test_perf_multisite.py b/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/manage_test_perf_multisite.py index f2abf51a5..f152ce220 100644 --- a/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/manage_test_perf_multisite.py +++ b/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/manage_test_perf_multisite.py @@ -54,7 +54,7 @@ def manage_test_perf_multisite( self):
- + manage_main / index_ger.html / etc.
From 1f41d9fc718a531b81d5a84aabf58b0b8b9a212d Mon Sep 17 00:00:00 2001 From: zmsdev <33760154+zmsdev@users.noreply.github.com> Date: Wed, 8 Jul 2026 18:31:54 +0200 Subject: [PATCH 5/6] prepared conf-manager --- Products/zms/_confmanager.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Products/zms/_confmanager.py b/Products/zms/_confmanager.py index a5c6b6f98..290203c98 100644 --- a/Products/zms/_confmanager.py +++ b/Products/zms/_confmanager.py @@ -647,6 +647,25 @@ def getReqProperty(self, key, default=None, REQUEST=None): return REQUEST.get(key, default) + def __aq_conf__(self, deep=0): + if deep == 0: + reqBuffId = 'CacheManager.__aq_conf__' + try: return self.fetchReqBuff(reqBuffId) + except: pass + aq_conf = self.get_conf_properties() + portalMaster = self.getPortalMaster() + if portalMaster: + # merge, portal master first to allow local override + master_conf = {k:v for k,v in portalMaster.__aq_conf__(deep+1).items() \ + if not k in UNINHERITED_PROPERTIES \ + and not k[:k.find('.')] in UNINHERITED_PROPERTIES \ + and not k in ['UniBE.Alias', 'UniBE.Server']} + aq_conf = {**master_conf, **aq_conf} + if deep == 0: + return self.storeReqBuff( reqBuffId, aq_conf) + return aq_conf + + def get_conf_property(self, *args, **kwargs): """ Return a configuration value, resolving local and inherited defaults. From 46d4803ad32d1bcb24e829d224b26fc947efa537 Mon Sep 17 00:00:00 2001 From: drfho Date: Thu, 9 Jul 2026 01:15:02 +0200 Subject: [PATCH 6/6] manage_test_perf_multisite-cmd: cleaned whitespace, gui details --- .../manage_test_perf_multisite/__init__.yaml | 2 +- .../manage_test_perf_multisite.py | 302 +++++++++--------- Products/zms/version.txt | 2 +- 3 files changed, 156 insertions(+), 150 deletions(-) diff --git a/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/__init__.yaml b/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/__init__.yaml index 135ce19ac..1b9dd7b14 100644 --- a/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/__init__.yaml +++ b/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/__init__.yaml @@ -8,7 +8,7 @@ manage_test_perf_multisite: name: Test Performance Multisite nodes: '{$}' package: com.zms.test.performance.multisite - revision: 0.0.1 + revision: 0.0.2 roles: - ZMSAdministrator title: Test Performance Multisite diff --git a/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/manage_test_perf_multisite.py b/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/manage_test_perf_multisite.py index f152ce220..f3ee57aec 100644 --- a/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/manage_test_perf_multisite.py +++ b/Products/zms/conf/metacmd_manager/manage_test_perf_multisite/manage_test_perf_multisite.py @@ -1,24 +1,30 @@ from Products.zms import standard -def manage_test_perf_multisite( self): - request = self.REQUEST - if request.get('shared_cache_id','') == 'shared_cache': - value = self.fetchSharedCache('ZMSMetaobjManager.__get_metaobjs__') - request.RESPONSE.setHeader('Content-Type','application/json; charset=utf-8') - return standard.str_json({'status':'ok','message':'Shared Cache is enabled.','value':value}) - prt = [] - prt.append('') - prt.append('') - prt.append(self.zmi_html_head(self,request)) - prt.append(''%self.zmi_body_class(id='')) - prt.append(self.zmi_body_header(self,request,options=self.customize_manage_options())) - prt.append('
') - prt.append(self.zmi_breadcrumbs(self,request,extra=[{'label':'Index','action':'manage_main'}])) - prt.append('
') - prt.append(''%request['lang']) - prt.append('Test Performance Multisite') - prt.append('
') - prt.append(""" +def manage_test_perf_multisite(self): + request = self.REQUEST + if request.get('shared_cache_id','') == 'shared_cache': + value = self.fetchSharedCache('ZMSMetaobjManager.__get_metaobjs__') + request.RESPONSE.setHeader('Content-Type','application/json; charset=utf-8') + return standard.str_json( + {'status':'ok', 'message':'Shared Cache is enabled.', 'value':dict(value)}, + encoding='utf-8', + level=1, + formatted=True + ) + + prt = [] + prt.append('') + prt.append('') + prt.append(self.zmi_html_head(self,request)) + prt.append(''%self.zmi_body_class(id='')) + prt.append(self.zmi_body_header(self,request,options=self.customize_manage_options())) + prt.append('
') + prt.append(self.zmi_breadcrumbs(self,request,extra=[{'label':'Index','action':'manage_main'}])) + prt.append('') + prt.append(''%request['lang']) + prt.append('Test Performance Multisite') + prt.append('
') + prt.append("""
@@ -51,46 +57,46 @@ def manage_test_perf_multisite( self):
-
- -
- - manage_main / index_ger.html / etc. -
-
-
- -
- -
-
-
- -
- - - - Shared Cache Test - -
-
- """) - - prt.append('
') - prt.append('') - prt.append('
') - prt.append(self.zmi_body_footer(self,request)) - prt.append(''' +
+ +
+ + manage_main / index_ger.html / etc. +
+
+
+ +
+ +
+
+
+ +
+ + + + Shared Cache + +
+
+ """) + + prt.append('
') + prt.append('') + prt.append('
') + prt.append(self.zmi_body_footer(self,request)) + prt.append(''' - ''') - prt.append('') - prt.append('') - - return '\n'.join(prt) \ No newline at end of file + ''') + prt.append('') + prt.append('') + + return '\n'.join(prt) \ No newline at end of file diff --git a/Products/zms/version.txt b/Products/zms/version.txt index fac066673..e75add346 100644 --- a/Products/zms/version.txt +++ b/Products/zms/version.txt @@ -1 +1 @@ -6.1.0+7d209b4 +6.1.0+f7d2a0f