From 7e28341bf0914672acdea5b84f5997a0da75e016 Mon Sep 17 00:00:00 2001 From: Andrew Hind Date: Thu, 1 Mar 2007 15:45:00 +0000 Subject: [PATCH] Improvements to cold permission performance and caching. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5247 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../impl/PermissionServiceImpl.java | 214 +++++++++++------- .../impl/model/PermissionModel.java | 42 +++- 2 files changed, 164 insertions(+), 92 deletions(-) diff --git a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java index 4adadcf05a..aece0dfdf7 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java @@ -20,7 +20,7 @@ * and Open Source Software ("FLOSS") applications as described in Alfresco's * FLOSS exception. You should have recieved a copy of the text describing * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" + * http://www.alfresco.com/legal/licensing */ package org.alfresco.repo.security.permissions.impl; @@ -61,23 +61,21 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; /** - * The Alfresco implementation of a permissions service against our APIs for the - * permissions model and permissions persistence. + * The Alfresco implementation of a permissions service against our APIs for the permissions model and permissions persistence. * * @author andyh */ public class PermissionServiceImpl implements PermissionServiceSPI, InitializingBean { - - static SimplePermissionReference OLD_ALL_PERMISSIONS_REFERENCE = new SimplePermissionReference( - QName.createQName("", PermissionService.ALL_PERMISSIONS), - PermissionService.ALL_PERMISSIONS); - + + static SimplePermissionReference OLD_ALL_PERMISSIONS_REFERENCE = new SimplePermissionReference(QName.createQName( + "", PermissionService.ALL_PERMISSIONS), PermissionService.ALL_PERMISSIONS); + private static Log log = LogFactory.getLog(PermissionServiceImpl.class); - + /** a transactionally-safe cache to be injected */ private SimpleCache accessCache; - + /* * Access to the model */ @@ -107,7 +105,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing * Access to the authority component */ private AuthorityService authorityService; - + /* * Dynamic authorities providers */ @@ -151,7 +149,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing { this.authenticationComponent = authenticationComponent; } - + public void setAuthorityService(AuthorityService authorityService) { this.authorityService = authorityService; @@ -177,12 +175,12 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing { this.policyComponent = policyComponent; } - + public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) { accessCache.clear(); } - + public void afterPropertiesSet() throws Exception { if (dictionaryService == null) @@ -205,9 +203,9 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing { throw new IllegalArgumentException("Property 'authenticationComponent' has not been set"); } - if(authorityService == null) + if (authorityService == null) { - throw new IllegalArgumentException("Property 'authorityService' has not been set"); + throw new IllegalArgumentException("Property 'authorityService' has not been set"); } if (accessCache == null) { @@ -217,9 +215,10 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing { throw new IllegalArgumentException("Property 'policyComponent' has not been set"); } - - policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onMoveNode"), ContentModel.TYPE_BASE, new JavaBehaviour(this, "onMoveNode")); - + + policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onMoveNode"), + ContentModel.TYPE_BASE, new JavaBehaviour(this, "onMoveNode")); + } // @@ -316,12 +315,11 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing { return authorityType; } - + @Override public String toString() { - return accessStatus + " " + this.permission + " - " + - this.authority + " (" + this.authorityType + ")"; + return accessStatus + " " + this.permission + " - " + this.authority + " (" + this.authorityType + ")"; } @Override @@ -390,36 +388,31 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing { return AccessStatus.DENIED; } - + // Allow permissions for nodes that do not exist if (!nodeService.exists(nodeRef)) { return AccessStatus.ALLOWED; } - - - + // Get the current authentications // Use the smart authentication cache to improve permissions performance Authentication auth = authenticationComponent.getCurrentAuthentication(); Set authorisations = getAuthorisations(auth, nodeRef); - - Serializable key = generateKey( - authorisations, - nodeRef, - perm); + + Serializable key = generateKey(authorisations, nodeRef, perm, CacheType.HAS_PERMISSION); AccessStatus status = accessCache.get(key); if (status != null) { return status; } - + // If the node does not support the given permission there is no point // doing the test Set available = modelDAO.getAllPermissions(nodeRef); available.add(getAllPermissionReference()); available.add(OLD_ALL_PERMISSIONS_REFERENCE); - + if (!(available.contains(perm))) { accessCache.put(key, AccessStatus.DENIED); @@ -428,16 +421,15 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing if (authenticationComponent.getCurrentUserName().equals(authenticationComponent.getSystemUserName())) { - return AccessStatus.ALLOWED; + return AccessStatus.ALLOWED; } - + // // TODO: Dynamic permissions via evaluators // /* - * Does the current authentication have the supplied permission on the - * given node. + * Does the current authentication have the supplied permission on the given node. */ QName typeQname = nodeService.getType(nodeRef); @@ -455,24 +447,28 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing + perm + "> is " + (result ? "allowed" : "denied") + " for " + authenticationComponent.getCurrentUserName() + " on node " + nodeService.getPath(nodeRef)); } - + status = result ? AccessStatus.ALLOWED : AccessStatus.DENIED; accessCache.put(key, status); return status; } - + + enum CacheType + { + HAS_PERMISSION, SINGLE_PERMISSION, SINGLE_PERMISSION_GLOBAL; + } + /** - * Key for a cache object is built from all the known Authorities (which can - * change dynamically so they must all be used) the NodeRef ID and the - * permission reference itself. This gives a unique key for each permission - * test. + * Key for a cache object is built from all the known Authorities (which can change dynamically so they must all be used) the NodeRef ID and the permission reference itself. + * This gives a unique key for each permission test. */ - static Serializable generateKey(Set auths, NodeRef nodeRef, PermissionReference perm) + static Serializable generateKey(Set auths, NodeRef nodeRef, PermissionReference perm, CacheType type) { LinkedHashSet key = new LinkedHashSet(); key.add(perm.toString()); - key.addAll(auths); + key.addAll(auths); key.add(nodeRef); + key.add(type); return key; } @@ -575,7 +571,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing permissionsDaoComponent.setInheritParentPermissions(nodeRef, inheritParentPermissions); accessCache.clear(); } - + /** * @see org.alfresco.service.cmr.security.PermissionService#getInheritParentPermissions(org.alfresco.service.cmr.repository.NodeRef) */ @@ -584,7 +580,6 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing return permissionsDaoComponent.getInheritParentPermissions(nodeRef); } - public PermissionReference getPermissionReference(QName qname, String permissionName) { return modelDAO.getPermissionReference(qname, permissionName); @@ -594,7 +589,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing { return getPermissionReference(ALL_PERMISSIONS); } - + public String getPermission(PermissionReference permissionReference) { if (modelDAO.isUnique(permissionReference)) @@ -642,7 +637,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing permissionsDaoComponent.deletePermissions(recipient); accessCache.clear(); } - + // // SUPPORT CLASSES // @@ -866,64 +861,114 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing public boolean hasSinglePermission(Set authorisations, NodeRef nodeRef) { + Serializable key = generateKey( + authorisations, + nodeRef, + this.required, CacheType.SINGLE_PERMISSION_GLOBAL); + + AccessStatus status = accessCache.get(key); + if (status != null) + { + return status == AccessStatus.ALLOWED; + } + // Check global permission if (checkGlobalPermissions(authorisations)) { + accessCache.put(key, AccessStatus.ALLOWED); return true; } Set> denied = new HashSet>(); - // Keep track of permission that are denied + return hasSinglePermission(authorisations, nodeRef, denied); + + } + + public boolean hasSinglePermission(Set authorisations, NodeRef nodeRef, + Set> denied) + { + // Add any denied permission to the denied list - these can not + // then + // be used to given authentication. + // A -> B -> C + // If B denies all permissions to any - allowing all permissions + // to + // andy at node A has no effect + + denied.addAll(getDenied(nodeRef)); + + // Cache non denied + Serializable key = null; + if (denied.size() == 0) + { + key = generateKey(authorisations, nodeRef, this.required, CacheType.SINGLE_PERMISSION); + } + if (key != null) + { + AccessStatus status = accessCache.get(key); + if (status != null) + { + return status == AccessStatus.ALLOWED; + } + } + + // If the current node allows the permission we are done + // The test includes any parent or ancestor requirements + if (checkRequired(authorisations, nodeRef, denied)) + { + if (key != null) + { + accessCache.put(key, AccessStatus.ALLOWED); + } + return true; + } // Permissions are only evaluated up the primary parent chain // TODO: Do not ignore non primary permissions ChildAssociationRef car = nodeService.getPrimaryParent(nodeRef); - // Work up the parent chain evaluating permissions. - while (car != null) + + // Build the next element of the evaluation chain + if (car.getParentRef() != null) { - // Add any denied permission to the denied list - these can not - // then - // be used to given authentication. - // A -> B -> C - // If B denies all permissions to any - allowing all permissions - // to - // andy at node A has no effect - - denied.addAll(getDenied(car.getChildRef())); - - // If the current node allows the permission we are done - // The test includes any parent or ancestor requirements - if (checkRequired(authorisations, car.getChildRef(), denied)) + NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(nodeRef); + if ((nodePermissions == null) || (nodePermissions.inheritPermissions())) { - return true; - } - - // Build the next element of the evaluation chain - if (car.getParentRef() != null) - { - NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(car.getChildRef()); - if ((nodePermissions == null) || (nodePermissions.inheritPermissions())) + if(hasSinglePermission(authorisations, car.getParentRef(), denied)) { - car = nodeService.getPrimaryParent(car.getParentRef()); + if (key != null) + { + accessCache.put(key, AccessStatus.ALLOWED); + } + return true; } else { - car = null; + if (key != null) + { + accessCache.put(key, AccessStatus.DENIED); + } + return false; } } else { - car = null; + if (key != null) + { + accessCache.put(key, AccessStatus.DENIED); + } + return false; } - } - - // TODO: Support meta data permissions on the root node? - - return false; - + else + { + if (key != null) + { + accessCache.put(key, AccessStatus.DENIED); + } + return false; + } } /** @@ -982,7 +1027,8 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing // All permission excludes all permissions available for // the node. - if (pe.getPermissionReference().equals(getAllPermissionReference()) || pe.getPermissionReference().equals(OLD_ALL_PERMISSIONS_REFERENCE)) + if (pe.getPermissionReference().equals(getAllPermissionReference()) + || pe.getPermissionReference().equals(OLD_ALL_PERMISSIONS_REFERENCE)) { for (PermissionReference deny : modelDAO.getAllPermissions(nodeRef)) { @@ -1072,7 +1118,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing // Default deny return false; } - + } /** diff --git a/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionModel.java b/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionModel.java index 75cbe7fb28..ecdc30d0bf 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionModel.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/model/PermissionModel.java @@ -20,12 +20,13 @@ * and Open Source Software ("FLOSS") applications as described in Alfresco's * FLOSS exception. You should have recieved a copy of the text describing * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" + * http://www.alfresco.com/legal/licensing */ package org.alfresco.repo.security.permissions.impl.model; import java.io.IOException; import java.io.InputStream; +import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -727,19 +728,44 @@ public class PermissionModel implements ModelDAO, InitializingBean } return pg; } + + static Serializable generateKey(PermissionReference required, QName qName, Set aspectQNames, + RequiredPermission.On on) + { + LinkedHashSet key = new LinkedHashSet(); + key.add(required.toString()); + key.add(qName); + key.addAll(aspectQNames); + key.add(on.toString()); + return key; + } + + + private HashMap> requiredPermissionsCache = new HashMap>( + 1024); public Set getRequiredPermissions(PermissionReference required, QName qName, Set aspectQNames, RequiredPermission.On on) { - PermissionGroup pg = getBasePermissionGroupOrNull(getPermissionGroupOrNull(required)); - if (pg == null) + // Cache lookup as this is static + + Serializable key = generateKey(required, qName, aspectQNames, on); + + Set answer = requiredPermissionsCache.get(key); + if (answer == null) { - return getRequirementsForPermission(required, on); - } - else - { - return getRequirementsForPermissionGroup(pg, on, qName, aspectQNames); + PermissionGroup pg = getBasePermissionGroupOrNull(getPermissionGroupOrNull(required)); + if (pg == null) + { + answer = getRequirementsForPermission(required, on); + } + else + { + answer = getRequirementsForPermissionGroup(pg, on, qName, aspectQNames); + } + requiredPermissionsCache.put(key, answer); } + return answer; } /**