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 * and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing * FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here: * 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; package org.alfresco.repo.security.permissions.impl;
@@ -61,17 +61,15 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
/** /**
* The Alfresco implementation of a permissions service against our APIs for the * The Alfresco implementation of a permissions service against our APIs for the permissions model and permissions persistence.
* permissions model and permissions persistence.
* *
* @author andyh * @author andyh
*/ */
public class PermissionServiceImpl implements PermissionServiceSPI, InitializingBean public class PermissionServiceImpl implements PermissionServiceSPI, InitializingBean
{ {
static SimplePermissionReference OLD_ALL_PERMISSIONS_REFERENCE = new SimplePermissionReference( static SimplePermissionReference OLD_ALL_PERMISSIONS_REFERENCE = new SimplePermissionReference(QName.createQName(
QName.createQName("", PermissionService.ALL_PERMISSIONS), "", PermissionService.ALL_PERMISSIONS), PermissionService.ALL_PERMISSIONS);
PermissionService.ALL_PERMISSIONS);
private static Log log = LogFactory.getLog(PermissionServiceImpl.class); 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"); 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");
} }
@@ -218,7 +216,8 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
throw new IllegalArgumentException("Property 'policyComponent' has not been set"); 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 @Override
public String toString() public String toString()
{ {
return accessStatus + " " + this.permission + " - " + return accessStatus + " " + this.permission + " - " + this.authority + " (" + this.authorityType + ")";
this.authority + " (" + this.authorityType + ")";
} }
@Override @Override
@@ -397,17 +395,12 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
return AccessStatus.ALLOWED; return AccessStatus.ALLOWED;
} }
// Get the current authentications // Get the current authentications
// Use the smart authentication cache to improve permissions performance // Use the smart authentication cache to improve permissions performance
Authentication auth = authenticationComponent.getCurrentAuthentication(); Authentication auth = authenticationComponent.getCurrentAuthentication();
Set<String> authorisations = getAuthorisations(auth, nodeRef); Set<String> authorisations = getAuthorisations(auth, nodeRef);
Serializable key = generateKey( Serializable key = generateKey(authorisations, nodeRef, perm, CacheType.HAS_PERMISSION);
authorisations,
nodeRef,
perm);
AccessStatus status = accessCache.get(key); AccessStatus status = accessCache.get(key);
if (status != null) if (status != null)
{ {
@@ -436,8 +429,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
// //
/* /*
* Does the current authentication have the supplied permission on the * Does the current authentication have the supplied permission on the given node.
* given node.
*/ */
QName typeQname = nodeService.getType(nodeRef); QName typeQname = nodeService.getType(nodeRef);
@@ -461,18 +453,22 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
return 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 * 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.
* change dynamically so they must all be used) the NodeRef ID and the * This gives a unique key for each permission test.
* 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>(); LinkedHashSet<Serializable> key = new LinkedHashSet<Serializable>();
key.add(perm.toString()); key.add(perm.toString());
key.addAll(auths); key.addAll(auths);
key.add(nodeRef); key.add(nodeRef);
key.add(type);
return key; return key;
} }
@@ -584,7 +580,6 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
return permissionsDaoComponent.getInheritParentPermissions(nodeRef); return permissionsDaoComponent.getInheritParentPermissions(nodeRef);
} }
public PermissionReference getPermissionReference(QName qname, String permissionName) public PermissionReference getPermissionReference(QName qname, String permissionName)
{ {
return modelDAO.getPermissionReference(qname, permissionName); return modelDAO.getPermissionReference(qname, permissionName);
@@ -866,22 +861,33 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
public boolean hasSinglePermission(Set<String> authorisations, NodeRef nodeRef) 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 // Check global permission
if (checkGlobalPermissions(authorisations)) if (checkGlobalPermissions(authorisations))
{ {
accessCache.put(key, AccessStatus.ALLOWED);
return true; return true;
} }
Set<Pair<String, PermissionReference>> denied = new HashSet<Pair<String, PermissionReference>>(); 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); public boolean hasSinglePermission(Set<String> authorisations, NodeRef nodeRef,
// Work up the parent chain evaluating permissions. Set<Pair<String, PermissionReference>> denied)
while (car != null)
{ {
// Add any denied permission to the denied list - these can not // Add any denied permission to the denied list - these can not
// then // then
@@ -891,39 +897,78 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
// to // to
// andy at node A has no effect // 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 // If the current node allows the permission we are done
// The test includes any parent or ancestor requirements // 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; 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 // Build the next element of the evaluation chain
if (car.getParentRef() != null) if (car.getParentRef() != null)
{ {
NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(car.getChildRef()); NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(nodeRef);
if ((nodePermissions == null) || (nodePermissions.inheritPermissions())) 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 else
{ {
car = null; if (key != null)
}
}
else
{ {
car = null; accessCache.put(key, AccessStatus.DENIED);
} }
}
// TODO: Support meta data permissions on the root node?
return false; 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 // All permission excludes all permissions available for
// the node. // 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)) for (PermissionReference deny : modelDAO.getAllPermissions(nodeRef))
{ {

View File

@@ -20,12 +20,13 @@
* and Open Source Software ("FLOSS") applications as described in Alfresco's * and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing * FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here: * 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; package org.alfresco.repo.security.permissions.impl.model;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Serializable;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@@ -728,18 +729,43 @@ public class PermissionModel implements ModelDAO, InitializingBean
return pg; 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, public Set<PermissionReference> getRequiredPermissions(PermissionReference required, QName qName,
Set<QName> aspectQNames, RequiredPermission.On on) 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)); PermissionGroup pg = getBasePermissionGroupOrNull(getPermissionGroupOrNull(required));
if (pg == null) if (pg == null)
{ {
return getRequirementsForPermission(required, on); answer = getRequirementsForPermission(required, on);
} }
else else
{ {
return getRequirementsForPermissionGroup(pg, on, qName, aspectQNames); answer = getRequirementsForPermissionGroup(pg, on, qName, aspectQNames);
} }
requiredPermissionsCache.put(key, answer);
}
return answer;
} }
/** /**