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
This commit is contained in:
Andrew Hind
2007-03-01 15:45:00 +00:00
parent 00cb31a85a
commit 7e28341bf0
2 changed files with 164 additions and 92 deletions

View File

@@ -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,17 +61,15 @@ 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);
@@ -205,7 +203,7 @@ 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");
}
@@ -218,7 +216,8 @@ 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"));
}
@@ -320,8 +319,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
@Override
public String toString()
{
return accessStatus + " " + this.permission + " - " +
this.authority + " (" + this.authorityType + ")";
return accessStatus + " " + this.permission + " - " + this.authority + " (" + this.authorityType + ")";
}
@Override
@@ -397,17 +395,12 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
return AccessStatus.ALLOWED;
}
// Get the current authentications
// Use the smart authentication cache to improve permissions performance
Authentication auth = authenticationComponent.getCurrentAuthentication();
Set<String> 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)
{
@@ -436,8 +429,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
//
/*
* 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);
@@ -461,18 +453,22 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
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<String> auths, NodeRef nodeRef, PermissionReference perm)
static Serializable generateKey(Set<String> auths, NodeRef nodeRef, PermissionReference perm, CacheType type)
{
LinkedHashSet<Serializable> key = new LinkedHashSet<Serializable>();
key.add(perm.toString());
key.addAll(auths);
key.add(nodeRef);
key.add(type);
return key;
}
@@ -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);
@@ -866,22 +861,33 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
public boolean hasSinglePermission(Set<String> 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<Pair<String, PermissionReference>> denied = new HashSet<Pair<String, PermissionReference>>();
// Keep track of permission that are denied
return hasSinglePermission(authorisations, nodeRef, denied);
// 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)
}
public boolean hasSinglePermission(Set<String> authorisations, NodeRef nodeRef,
Set<Pair<String, PermissionReference>> denied)
{
// Add any denied permission to the denied list - these can not
// then
@@ -891,39 +897,78 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
// to
// andy at node A has no effect
denied.addAll(getDenied(car.getChildRef()));
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, car.getChildRef(), denied))
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);
// Build the next element of the evaluation chain
if (car.getParentRef() != null)
{
NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(car.getChildRef());
NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(nodeRef);
if ((nodePermissions == null) || (nodePermissions.inheritPermissions()))
{
car = nodeService.getPrimaryParent(car.getParentRef());
if(hasSinglePermission(authorisations, car.getParentRef(), denied))
{
if (key != null)
{
accessCache.put(key, AccessStatus.ALLOWED);
}
return true;
}
else
{
car = null;
}
}
else
if (key != null)
{
car = null;
accessCache.put(key, AccessStatus.DENIED);
}
}
// TODO: Support meta data permissions on the root node?
return false;
}
}
else
{
if (key != null)
{
accessCache.put(key, AccessStatus.DENIED);
}
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))
{

View File

@@ -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;
@@ -728,18 +729,43 @@ public class PermissionModel implements ModelDAO, InitializingBean
return pg;
}
static Serializable generateKey(PermissionReference required, QName qName, Set<QName> aspectQNames,
RequiredPermission.On on)
{
LinkedHashSet<Serializable> key = new LinkedHashSet<Serializable>();
key.add(required.toString());
key.add(qName);
key.addAll(aspectQNames);
key.add(on.toString());
return key;
}
private HashMap<Serializable, Set<PermissionReference>> requiredPermissionsCache = new HashMap<Serializable, Set<PermissionReference>>(
1024);
public Set<PermissionReference> getRequiredPermissions(PermissionReference required, QName qName,
Set<QName> aspectQNames, RequiredPermission.On on)
{
// Cache lookup as this is static
Serializable key = generateKey(required, qName, aspectQNames, on);
Set<PermissionReference> answer = requiredPermissionsCache.get(key);
if (answer == null)
{
PermissionGroup pg = getBasePermissionGroupOrNull(getPermissionGroupOrNull(required));
if (pg == null)
{
return getRequirementsForPermission(required, on);
answer = getRequirementsForPermission(required, on);
}
else
{
return getRequirementsForPermissionGroup(pg, on, qName, aspectQNames);
answer = getRequirementsForPermissionGroup(pg, on, qName, aspectQNames);
}
requiredPermissionsCache.put(key, answer);
}
return answer;
}
/**