Index: kernel-component/src/main/webapp/WEB-INF/authz-components.xml =================================================================== --- kernel-component/src/main/webapp/WEB-INF/authz-components.xml (revision 309143) +++ kernel-component/src/main/webapp/WEB-INF/authz-components.xml (working copy) @@ -51,6 +51,7 @@ + 5 Index: kernel-impl/src/main/java/org/sakaiproject/authz/impl/BaseAuthzGroup.java =================================================================== --- kernel-impl/src/main/java/org/sakaiproject/authz/impl/BaseAuthzGroup.java (revision 309143) +++ kernel-impl/src/main/java/org/sakaiproject/authz/impl/BaseAuthzGroup.java (working copy) @@ -21,22 +21,10 @@ package org.sakaiproject.authz.impl; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.Stack; - import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.sakaiproject.authz.api.AuthzGroup; -import org.sakaiproject.authz.api.AuthzGroupService; -import org.sakaiproject.authz.api.Member; -import org.sakaiproject.authz.api.Role; -import org.sakaiproject.authz.api.RoleAlreadyDefinedException; +import org.sakaiproject.authz.api.*; import org.sakaiproject.entity.api.Reference; import org.sakaiproject.entity.api.ResourceProperties; import org.sakaiproject.entity.api.ResourcePropertiesEdit; @@ -52,6 +40,8 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import java.util.*; + /** *

* BaseAuthzGroup is an implementation of the AuthGroup API AuthzGroup. @@ -114,6 +104,9 @@ private UserDirectoryService userDirectoryService; + /** The most recently changed set of role/functions - ONLY valid during the save event processing on the same server */ + public Set m_lastChangedRlFn; + /** * Construct. * Index: kernel-impl/src/main/java/org/sakaiproject/authz/impl/BaseAuthzGroupService.java =================================================================== --- kernel-impl/src/main/java/org/sakaiproject/authz/impl/BaseAuthzGroupService.java (revision 309143) +++ kernel-impl/src/main/java/org/sakaiproject/authz/impl/BaseAuthzGroupService.java (working copy) @@ -21,40 +21,12 @@ package org.sakaiproject.authz.impl; -import java.util.Collection; -import java.util.Collections; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Stack; -import java.util.Vector; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.sakaiproject.authz.api.AuthzGroup; -import org.sakaiproject.authz.api.AuthzGroupAdvisor; -import org.sakaiproject.authz.api.AuthzGroupService; -import org.sakaiproject.authz.api.AuthzPermissionException; -import org.sakaiproject.authz.api.FunctionManager; -import org.sakaiproject.authz.api.GroupAlreadyDefinedException; -import org.sakaiproject.authz.api.GroupFullException; -import org.sakaiproject.authz.api.GroupIdInvalidException; -import org.sakaiproject.authz.api.GroupNotDefinedException; -import org.sakaiproject.authz.api.GroupProvider; -import org.sakaiproject.authz.api.Role; -import org.sakaiproject.authz.api.RoleAlreadyDefinedException; -import org.sakaiproject.authz.api.SecurityService; +import org.sakaiproject.authz.api.*; import org.sakaiproject.component.api.ServerConfigurationService; import org.sakaiproject.component.cover.ComponentManager; -import org.sakaiproject.entity.api.Edit; -import org.sakaiproject.entity.api.Entity; -import org.sakaiproject.entity.api.EntityManager; -import org.sakaiproject.entity.api.HttpAccess; -import org.sakaiproject.entity.api.Reference; -import org.sakaiproject.entity.api.ResourceProperties; +import org.sakaiproject.entity.api.*; import org.sakaiproject.event.api.EventTrackingService; import org.sakaiproject.exception.PermissionException; import org.sakaiproject.javax.PagingPosition; @@ -64,10 +36,11 @@ import org.sakaiproject.tool.api.SessionManager; import org.sakaiproject.user.api.UserDirectoryService; import org.sakaiproject.user.api.UserNotDefinedException; -import org.sakaiproject.util.StorageUser; import org.w3c.dom.Document; import org.w3c.dom.Element; +import java.util.*; + /** *

* BaseAuthzGroupService is a Sakai azGroup service implementation. @@ -626,10 +599,32 @@ // track it String event = ((BaseAuthzGroup) azGroup).getEvent(); if (event == null) event = SECURE_UPDATE_AUTHZ_GROUP; + // KNL-1230 handle changes to authzgroups by processing caching changes + if (SECURE_UPDATE_AUTHZ_GROUP.equals(event)) { + // we only care about update events here + try { + HashSet roles = null; + HashSet permissions = null; + Set lastChangedPerms = ((BaseAuthzGroup) azGroup).m_lastChangedRlFn; + if (lastChangedPerms != null && !lastChangedPerms.isEmpty()) { + roles = new HashSet(); + permissions = new HashSet(lastChangedPerms.size()); + for (DbAuthzGroupService.DbStorage.RoleAndFunction rf : lastChangedPerms) { + permissions.add(rf.function); + roles.add(rf.role); + } + M_log.info("Changed permissions for roles (" + roles + ") in " + azGroup.getId() + ": " + permissions); + } + ((SakaiSecurity) securityService()).notifyRealmChanged(azGroup.getId(), roles, permissions); + } catch (Exception e) { + M_log.warn("Failure while trying to notify SS about realm changes for AZG(" + azGroup.getId() + "): " + e, e); + } + } // End KNL-1230 eventTrackingService().post(eventTrackingService().newEvent(event, azGroup.getReference(), true)); // close the azGroup object ((BaseAuthzGroup) azGroup).closeEdit(); + ((BaseAuthzGroup) azGroup).m_lastChangedRlFn = null; // cleanup // update the db with latest provider, and site security with the latest changes, using the updated azGroup BaseAuthzGroup updatedRealm = (BaseAuthzGroup) m_storage.get(azGroup.getId()); @@ -833,7 +828,12 @@ for (AuthzGroupAdvisor authzGroupAdvisor : authzGroupAdvisors) { authzGroupAdvisor.remove(azGroup); } - + // KNL-1230 handle removal of authzgroups by processing caching changes + try { + ((SakaiSecurity) securityService()).notifyRealmRemoved(azGroup.getId()); + } catch (Exception e) { + M_log.warn("Failure while trying to notify SS about realm removal for AZG(" + azGroup.getId() + "): " + e, e); + } // End KNL-1230 // complete the azGroup m_storage.remove(azGroup); @@ -861,25 +861,7 @@ return; } - // check security (throws if not permitted) - unlock(SECURE_REMOVE_AUTHZ_GROUP, authzGroupReference(azGroupId)); - - // allow any advisors to make last minute changes - for (AuthzGroupAdvisor authzGroupAdvisor : authzGroupAdvisors) { - authzGroupAdvisor.remove(azGroup); - } - - // complete the azGroup - m_storage.remove(azGroup); - - // track it - eventTrackingService().post(eventTrackingService().newEvent(SECURE_REMOVE_AUTHZ_GROUP, azGroup.getReference(), true)); - - // close the azGroup object - ((BaseAuthzGroup) azGroup).closeEdit(); - - // clear any site security based on this (if a site) azGroup - removeSiteSecurity(azGroup); + removeAuthzGroup(azGroup); } /** Index: kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java =================================================================== --- kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java (revision 309143) +++ kernel-impl/src/main/java/org/sakaiproject/authz/impl/DbAuthzGroupService.java (working copy) @@ -1040,6 +1040,15 @@ return new HashSet(); } + if ("".equals(lock) || "*".equals(lock)) { + // SPECIAL CASE - return all authzGroup IDs this user is active in (much faster) + String statement = dbAuthzGroupSql.getSelectRealmUserGroupSql("SAKAI_REALM_RL_GR.ACTIVE = '1'"); + Object[] fields = new Object[1]; + fields[0] = userId; + List dbResult = sqlService().dbRead(statement, fields, null ); + return new HashSet(dbResult); + } + // Just like unlock, except we use all realms and get their ids // Note: consider over all realms just those realms where there's a grant of a role that satisfies the lock // Ignore realms where anon or auth satisfy the lock. @@ -1397,6 +1406,16 @@ fields[2] = getValueForSubquery(dbAuthzGroupSql.getInsertRealmRoleFunction3Sql(), raf.function); m_sql.dbWrite(sql, fields); } + + // KNL-1230 need to be able to tell when changes occur in the AZG + HashSet lastChanged = new HashSet(); + if (!toAdd.isEmpty()) { + lastChanged.addAll(toAdd); + } + if (!toDelete.isEmpty()) { + lastChanged.addAll(toDelete); + } + ((BaseAuthzGroup) azg).m_lastChangedRlFn = lastChanged; } protected void save_REALM_RL_GR(AuthzGroup azg) Index: kernel-impl/src/main/java/org/sakaiproject/authz/impl/SakaiSecurity.java =================================================================== --- kernel-impl/src/main/java/org/sakaiproject/authz/impl/SakaiSecurity.java (revision 309143) +++ kernel-impl/src/main/java/org/sakaiproject/authz/impl/SakaiSecurity.java (working copy) @@ -23,13 +23,12 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.sakaiproject.authz.api.AuthzGroupService; -import org.sakaiproject.authz.api.SecurityAdvisor; -import org.sakaiproject.authz.api.SecurityService; +import org.sakaiproject.authz.api.*; import org.sakaiproject.entity.api.Entity; import org.sakaiproject.entity.api.EntityManager; import org.sakaiproject.entity.api.Reference; import org.sakaiproject.event.api.EventTrackingService; +import org.sakaiproject.memory.api.Cache; import org.sakaiproject.memory.api.GenericMultiRefCache; import org.sakaiproject.memory.api.MemoryService; import org.sakaiproject.site.api.SiteService; @@ -102,6 +101,8 @@ */ protected abstract EventTrackingService eventTrackingService(); + protected abstract FunctionManager functionManager(); + /********************************************************************************************************************************************************************************************************************************************************** * Configuration *********************************************************************************************************************************************************************************************************************************************************/ @@ -124,6 +125,7 @@ * Init and Destroy *********************************************************************************************************************************************************************************************************************************************************/ + /** * Final initialization, once all dependencies are set. */ @@ -132,11 +134,337 @@ // <= 0 minutes indicates no caching desired if (m_cacheMinutes > 0) { - m_callCache = memoryService().newGenericMultiRefCache( - "org.sakaiproject.authz.api.SecurityService.cache"); + org.sakaiproject.component.api.ServerConfigurationService scs = org.sakaiproject.component.cover.ServerConfigurationService.getInstance(); + legacyCaching = scs.getBoolean("memory.use.legacy", legacyCaching); // TODO remove this after 10 merge + m_callCache = memoryService().newGenericMultiRefCache("org.sakaiproject.authz.api.SecurityService.cache"); // switch to normal Cache after 10 + if (legacyCaching) { + M_log.info("Using legacy org.sakaiproject.authz.api.SecurityService.cache MultiRefCache, this cache has unsafe race conditions"); + } else { + // TODO uncomment line below and keep the next line also after 10 release + // m_callCache = memoryService().getCache("org.sakaiproject.authz.api.SecurityService.cache"); + m_superCache = memoryService().getCache("org.sakaiproject.authz.api.SecurityService.superCache"); + m_contentCache = memoryService().getCache("org.sakaiproject.authz.api.SecurityService.contentCache"); + } } } + /** + * TODO remove this + * If true then legacy caching is being used instead of the new stuff + */ + boolean legacyCaching = false; + + /** + * Cache for holding the super user check cached results + * Only used in the new caching system + */ + Cache m_superCache; + /** + * Cache for holding the content authz check cached results + * Only used in the new caching system + */ + Cache m_contentCache; + + /** + * KNL-1230 + * Get a permission check from the cache + * @param key the cache key (generated using makeCacheKey) + * @param isSuper true if this is a super user cache entry + * @return boolean value if found, null if not found in the cache + */ + Boolean getFromCache(String key, boolean isSuper) { + Boolean result = null; + if (m_callCache != null) { + if (legacyCaching) { // TODO remove after 10 + result = (Boolean) m_callCache.get(key); + } else { + if (isSuper) { + result = (Boolean) m_superCache.get(key); + } else { + if (key.contains("@/content")) { + result = (Boolean) m_contentCache.get(key); + } else { + result = (Boolean) m_callCache.get(key); + } + } + // see note below about forced cache expiration + } + } + return result; + } + + /** + * KNL-1230 + * Add a permission check to the cache + * @param key the cache key (generated using makeCacheKey) + * @param payload true if the permission is granted, false if not + * @param entityRef NOT USED after 10 + * @param dependRefs NOT USED after 10 + * @param isSuper true if this is a super user cache entry + */ + void addToCache(String key, Boolean payload, String entityRef, Collection dependRefs, boolean isSuper) { + if (m_callCache != null && key != null) { + if (legacyCaching) { // TODO remove after 10 + m_callCache.put(key, payload, entityRef, dependRefs); + } else { + if (isSuper) { + m_superCache.put(key, payload); + } else { + if (key.contains("@/content")) { + m_contentCache.put(key, payload); + } else { + m_callCache.put(key, payload); + } + } + // see note below about forced cache expiration + } + } + } + + /* KNL-1230: expiration happens based on the following plan: + if (user.template, site.helper, etc. change) then clear entire security cache + else if the perms in a site changes we loop through all possible site users and the changed permissions and remove all those entries from the cache (including the entry for the anon user - e.g. unlock@@...) + else if the perms for a user change, same as site perms but all the user sites and the changed permissions + else if a user is added/removed from super user status then update the cache entry (easiest to simply make sure we update the cache when this happens rather than invalidating) + NOTES: + Cache keys are: unlock@{userId}@{perm}@{realm} AND super@{userId} + This strategy eliminates the need to store the invalidation keys and is much simpler to code + There is a very good chance many of those would not be in the cache but that should not cause a problem (however if it proves to be problematic we could do key checks to cut those down, but I don't think that is actually more efficient) + Getting all possible perms is cheap, that's in memory already + Get all siteids for a user or all userids for a site might be a little more costly, but the idea is that this is a rare case + Super user change is event: SiteService.SECURE_UPDATE_SITE_MEMBERSHIP with context !/site/admin + */ + + /** + * KNL-1230 + * Called when realms are changed (like a realm.upd Event), should handle inputs outside the range + * @param azgReference should be the value from Event.ref + * @param roles a set of roles that changed (may be null or empty) + * @param permissions a set of permissions that changed (may be null or empty) + * @return true if this was a realm and case we handle and we took action, false otherwise + */ + public boolean notifyRealmChanged(String azgReference, Set roles, Set permissions) { + if (m_callCache == null || legacyCaching) return false; // do nothing if old service is in use or no cache in use + if (azgReference != null) { + String ref = convertRealmRefToRef(azgReference); // strip off /realm/ from start + if ("!site.helper".equals(ref) + || ref.startsWith("!user.template") + //|| "/site/!site".equals(ref) // we might not need this one + ) { + if (permissions != null && !permissions.isEmpty()) { + // when the !site.helper or !user.template change then we need to just wipe the entire cache, this is a rare event + m_callCache.clear(); + return true; + } + + } else if ("/site/!admin".equals(ref)) { + // when the super user realm (!admin, also the event context) changes (realm.upd) then we wipe this cache out + if (m_superCache != null) { + m_superCache.clear(); + } + return true; + + } else if (ref.startsWith("/content")) { + // content realms require special handling + // WARNING: this is handled in a simple but not very efficient way, should be improved later + m_contentCache.clear(); + return true; + + } else { + if (permissions != null && !permissions.isEmpty()) { + // we only process the event change when there are changed permissions + cacheRealmPermsChanged(ref, roles, permissions); + return true; + } + } + } + return false; + } + + /** + * KNL-1230 + * Called when realms are removed (like a realm.del Event) + * @param azgReference should be the value from Event.ref + * @return true if this was a realm and case we handle and we took action, false otherwise + */ + public boolean notifyRealmRemoved(String azgReference) { + if (m_callCache == null || legacyCaching) return false; // do nothing if old service is in use or no cache in use + if (azgReference != null) { + String ref = convertRealmRefToRef(azgReference); // strip off /realm/ from start + if (ref.startsWith("/content")) { + // content realms require special handling + // WARNING: this is handled in a simple but not very efficient way, should be improved later + m_contentCache.clear(); + return true; + + } else { + // we only process the event change when there are changed permissions + cacheRealmPermsChanged(ref, null, null); + return true; + } + } + return false; + } + + /* Don't think we need this right now but leaving it for future ref just in case -AZ + void cacheUserPermsChanged(String userRef, Set roles, Set permissions) { + if (m_callCache == null || legacyCaching) return; // do nothing if old service is in use or no cache in use + // changed he permissions for a user + if (permissions == null || permissions.isEmpty()) { + List allPerms = functionManager().getRegisteredFunctions(); + permissions = new HashSet(allPerms); + } + String userId = userRef.substring(6); + HashSet keysToInvalidate = new HashSet(); + // get all azgs for this user + Set azgRefs = authzGroupService().getAuthzGroupsIsAllowed(userId, "*", null); // "*" means ANY permission + for (String ref : azgRefs) { + for (String perm : permissions) { + if (perm != null) { + keysToInvalidate.add(makeCacheKey(userId, perm, ref, false)); + } + } + } + // invalidate all keys (do this as a batch) + m_callCache.removeAll(keysToInvalidate); + } + */ + + /** + * KNL-1230 + * Flush out unlock check caches based on changes to the permissions in an AuthzGroup + * @param realmRef an AuthzGroup realm reference (e.g. /site/123123-as-sda21-213-1-33233) + * @param roles a set of roles that changed (may be null or empty) + * @param permissions a set of permissions that changed (may be null or empty) + */ + void cacheRealmPermsChanged(String realmRef, Set roles, Set permissions) { + if (m_callCache == null || legacyCaching) return; // do nothing if old service is in use or no cache in use + String azgRef = convertRealmRefToRef(realmRef); + if (permissions == null || permissions.isEmpty()) { + List allPerms = functionManager().getRegisteredFunctions(); + permissions = new HashSet(allPerms); + } + HashSet keysToInvalidate = new HashSet(); + // changed permissions for a role in an AZG + AuthzGroup azg; + try { + azg = authzGroupService().getAuthzGroup(azgRef); + } catch (GroupNotDefinedException e) { + // no group found so no invalidation needed + return; // SHORT CIRCUIT + } + // first handle the .anon and .auth (maybe only needed for special cases?) + if (roles.contains(AuthzGroupService.AUTH_ROLE)) { + /* .auth (AUTH_ROLE) is a special case, + * it could mean any possible user in the system so we cannot know which keys to invalidate. + * We have to just flush the entire cache + */ + m_callCache.clear(); + return; // SHORT CIRCUIT + } + boolean anon = false; + if (roles.contains(AuthzGroupService.ANON_ROLE)) { + anon = true; + } + if (!anon) { + Set azgRoles = azg.getRoles(); + for (Role role : azgRoles) { + if (AuthzGroupService.ANON_ROLE.equals(role.getId())) { + anon = true; + break; + } + } + } + if (anon) { + // anonymous user access (ANON_ROLE) needs to force reset on anonymous changes in the site + for (String perm : permissions) { + if (perm != null) { + keysToInvalidate.add(makeCacheKey(null, perm, azgRef, false)); + } + } + } + // now handle all the real users + Set members = azg.getMembers(); + if (members != null && !members.isEmpty()) { + for (Member member : members) { + if (member != null && member.isActive() && member.getUserId() != null) { + for (String perm : permissions) { + if (perm != null) { + keysToInvalidate.add(makeCacheKey(member.getUserId(), perm, azgRef, false)); + } + } + } + } + } + // invalidate all keys (do this as a batch) + m_callCache.removeAll(keysToInvalidate); + } + + /** + * KNL-1230 + * Convert a realm reference in to a standard reference + * @param realmRef a realm specific ref (e.g. /realm//site/123123-as-sda21-213-1-33233) + * @return a standard ref (e.g. /site/123123-as-sda21-213-1-33233) + */ + String convertRealmRefToRef(String realmRef) { + String rv = null; + if (realmRef != null) { + // strip off the leading /realm or /realm/ + if (realmRef.startsWith("/realm/")) { + rv = realmRef.substring(7); + } else if (realmRef.startsWith("/realm")) { + rv = realmRef.substring(6); + } else { + rv = realmRef; + } + } + return rv; + } + + /** + * KNL-1230 + * Make a cache key for security caching + * @param userId the internal sakai user ID (can be null) + * @param function the permission + * @param reference the realm reference + * @param isSuperKey if true this is a key for tracking super users, else generate a normal realm key + * @return the key OR null if one cannot be properly made from these params + */ + String makeCacheKey(String userId, String function, String reference, boolean isSuperKey) { + if (isSuperKey) { + if (userId != null) { + return "super@" + userId; + } else { + return null; + } + } + if (function == null || reference == null) { + return null; + } + // NOTE: userId can be null for this, others cannot be + return "unlock@" + userId + "@" + function + "@" + reference; + } + + /** + * Converts a collection of authzgroup ids into authzgroup references + * Added when removing the old MultiRefCache - KNL-1162 + * + * @param azgIds a collection of authzgroup ids + * @return a collection of authzgroup references (should match the incoming set of ids) + */ + protected Collection makeAzgRefsForAzgIds(Collection azgIds) { + // make refs for any azg ids + Collection azgRefs = null; + if (azgIds != null) { + azgRefs = new HashSet(azgIds.size()); + for (String azgId : azgIds) { + azgRefs.add(authzGroupService().authzGroupReference(azgId)); + } + } + return azgRefs; + } + + /** * Final cleanup. */ @@ -143,6 +471,9 @@ public void destroy() { M_log.info("destroy()"); + if (m_callCache != null) m_callCache.close(); + if (m_superCache != null) m_superCache.close(); + if (m_contentCache != null) m_contentCache.close(); } /********************************************************************************************************************************************************************************************************************************************************** @@ -169,10 +500,10 @@ if ((userId == null) || (userId.length() == 0)) return false; // check the cache - String command = "super@" + userId; + String command = makeCacheKey(userId, null, null, true); if (m_callCache != null) { - final Boolean value = (Boolean) m_callCache.get(command); + final Boolean value = getFromCache(command, true); if(value != null) return value.booleanValue(); } @@ -203,7 +534,7 @@ { Collection azgIds = new HashSet(); azgIds.add("/site/!admin"); - m_callCache.put(command, rv, null, makeAzgRefsForAzgIds(azgIds)); + addToCache(command, rv, null, makeAzgRefsForAzgIds(azgIds), true); } return rv; @@ -286,11 +617,11 @@ protected boolean checkAuthzGroups(String userId, String function, String entityRef, Collection azgs) { // check the cache - String command = "unlock@" + userId + "@" + function + "@" + entityRef; + String command = makeCacheKey(userId, function, entityRef, false); if (m_callCache != null) { - final Boolean value = (Boolean) m_callCache.get(command); + final Boolean value = getFromCache(command, false); if(value != null) return value.booleanValue(); } @@ -306,30 +637,11 @@ boolean rv = authzGroupService().isAllowed(userId, function, azgs); // cache - if (m_callCache != null) m_callCache.put(command, rv, entityRef, makeAzgRefsForAzgIds(azgs)); + addToCache(command, rv, entityRef, makeAzgRefsForAzgIds(azgs), false); return rv; } - /** - * Converts a collection of authzgroup ids into authzgroup references - * Added when removing the old MultiRefCache - KNL-1162 - * - * @param azgIds a collection of authzgroup ids - * @return a collection of authzgroup references (should match the incoming set of ids) - */ - protected Collection makeAzgRefsForAzgIds(Collection azgIds) { - // make refs for any azg ids - Collection azgRefs = null; - if (azgIds != null) { - azgRefs = new HashSet(azgIds.size()); - for (String azgId : azgIds) { - azgRefs.add(authzGroupService().authzGroupReference(azgId)); - } - } - return azgRefs; - } - /** * Access the List the Users who can unlock the lock for use with this resource. * @@ -568,7 +880,7 @@ eventTrackingService().post(eventTrackingService().newEvent(EVENT_ROLESWAP_CLEAR, org.sakaiproject.authz.api.AuthzGroupService.REFERENCE_ROOT + Entity.SEPARATOR + azGroupId, true)); - + return; } } Index: kernel-impl/src/main/java/org/sakaiproject/authz/impl/SakaiSecurityTest.java =================================================================== --- kernel-impl/src/main/java/org/sakaiproject/authz/impl/SakaiSecurityTest.java (revision 309143) +++ kernel-impl/src/main/java/org/sakaiproject/authz/impl/SakaiSecurityTest.java (working copy) @@ -22,6 +22,7 @@ package org.sakaiproject.authz.impl; import org.sakaiproject.authz.api.AuthzGroupService; +import org.sakaiproject.authz.api.FunctionManager; import org.sakaiproject.entity.api.EntityManager; import org.sakaiproject.event.api.EventTrackingService; import org.sakaiproject.memory.api.MemoryService; @@ -91,4 +92,9 @@ { return null; } + + @Override + protected FunctionManager functionManager() { + return null; + } } Index: kernel-impl/src/main/java/org/sakaiproject/memory/impl/GenericMultiRefCacheImpl.java =================================================================== --- kernel-impl/src/main/java/org/sakaiproject/memory/impl/GenericMultiRefCacheImpl.java (revision 309143) +++ kernel-impl/src/main/java/org/sakaiproject/memory/impl/GenericMultiRefCacheImpl.java (working copy) @@ -92,6 +92,7 @@ addRefCachedKey(dependRef, key); } } + if (mrcDebug) logCacheState("put("+key+")"); } /** @@ -165,6 +166,7 @@ } } } + if (mrcDebug) logCacheState("cleanEntityReferences("+key+")"); } /** @@ -376,6 +378,50 @@ { return m_refs; } - } + @Override + public String toString() { + return "MRCE["+this.get()+",refs("+(this.m_refs!=null?this.m_refs.size():"--")+")="+this.m_refs+"]"; + } + } + + // KNL-1230 added to assist with debugging caching issues + boolean mrcDebug = false; // always set to false in committed code + boolean mrcDebugDetailed = false; + void logCacheState(String operator) { + if (mrcDebug) { + String name = this.cache.getName(); + StringBuilder refsSB = new StringBuilder(); + refsSB.append(" * keys(").append(m_refsStore.keySet().size()).append("):").append(m_refsStore.keySet()).append("\n"); + int countRefs = 0; + for (Map.Entry> entry : m_refsStore.entrySet()) { + if (entry == null) continue; + int count = 0; + if (entry.getValue() != null) { + count = entry.getValue().size(); + } + countRefs += count; + if (mrcDebugDetailed) { + refsSB.append(" ").append(entry.getKey()).append(" => (").append(count).append(")").append(entry.getValue()).append("\n"); + } + } + StringBuilder entriesSB = new StringBuilder(); + List keys = this.cache.getKeys(); + entriesSB.append(" * keys(").append(keys.size()).append("):").append(new ArrayList(keys)).append("\n"); + Collection entries = this.cache.getAll(keys).values(); + int countMaps = 0; + for (Element element : entries) { + if (element == null) continue; + int count = 0; + if (element.getObjectValue() != null && element.getObjectValue() instanceof MultiRefCacheEntry) { + count = ((MultiRefCacheEntry)element.getObjectValue()).getRefs().size(); + } + countMaps += count; + if (mrcDebugDetailed) { + entriesSB.append(" ").append(element.getObjectKey()).append(" => (").append(count).append(")").append(element.getObjectValue()).append("\n"); + } + } + M_log.info("MRC:"+name+":: "+operator+" ::\n refsStore(Map[key => value],"+m_refsStore.size()+" + "+countRefs+" = "+(m_refsStore.size()+countRefs)+"):\n"+refsSB+"\n entries(Ehcache[key => payload],"+keys.size()+" + "+countMaps+" = "+(keys.size()+countMaps)+"):\n"+entriesSB); + } + } }