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..b79757365 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,31 @@ 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 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): """ @@ -75,11 +154,17 @@ 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. + 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) @@ -96,11 +181,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_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/_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. 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 c3ae986d2..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,18 +1,30 @@ -def manage_test_perf_multisite( self): - request = self.REQUEST - 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('