From eff2ff343c07f23bec5076a6d67d1fc72d22e47a Mon Sep 17 00:00:00 2001 From: Kevin Roast Date: Fri, 6 Jan 2006 17:28:23 +0000 Subject: [PATCH] Re-added the "templatable" aspect back into the web-client config XML Refactored cache in Permissions service so it can be re-used by other services e.g. Ownable Added cache into OwnableService (Andy to validate on Monday) git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2081 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../alfresco/repo/cache/AutoExpireCache.java | 151 ++++++++++++++++++ .../repo/ownable/impl/OwnableServiceImpl.java | 46 ++++-- .../impl/PermissionServiceImpl.java | 125 ++------------- 3 files changed, 197 insertions(+), 125 deletions(-) create mode 100644 source/java/org/alfresco/repo/cache/AutoExpireCache.java diff --git a/source/java/org/alfresco/repo/cache/AutoExpireCache.java b/source/java/org/alfresco/repo/cache/AutoExpireCache.java new file mode 100644 index 0000000000..769ebeb918 --- /dev/null +++ b/source/java/org/alfresco/repo/cache/AutoExpireCache.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.cache; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * A cache implementation that maintains items up to a threshold size. If the threshold size is reached + * it begins removing old items during get() calls as they time-out after a specified timeout value. + *

+ * If the threshold value is not reached, the items are not removed unless specifically requested with + * a call to remove() or clear(). + *

+ * If the max size value is reached then no more items are added to the cache until some are removed + * either explicitly or automically via timed-out values. + * + * @author Kevin Roast + */ +public class AutoExpireCache implements SimpleCache +{ + // TODO: configure these values via Spring + private final long TIMEDIFF = 1000000L * 1000L * 60L * 5L; // 5 mins in nano-seconds + private final int MAXSIZE = 4096; // maximum size of the cache + private final float THRESHOLD = 0.75f; // before we start removing items + private int maxsize = MAXSIZE; + private float threshold = THRESHOLD; + private Map> cache = new HashMap>(maxsize, 1.0f); + + /** + * Default constructor + */ + public AutoExpireCache() + { + } + + /** + * Constructor + */ + public AutoExpireCache(int maxsize, float threshold) + { + maxsize = maxsize; + threshold = threshold; + } + + /** + * @see org.alfresco.repo.cache.SimpleCache#get(K) + */ + public synchronized V get(K key) + { + CacheItem wrapper = cache.get(key); + if (wrapper != null) + { + // we cache values till a specific timeout then remove them + // this also gives other values a chance to get added if we are nearing the max size + if (cache.size() > (maxsize * threshold) && + System.nanoTime() > (wrapper.Timestamp + TIMEDIFF)) + { + //if (log.isDebugEnabled()) + // log.debug("*****Removing timedout key: " + key); + cache.remove(key); + wrapper = null; + } + } + return wrapper != null ? wrapper.Value : null; + } + + /** + * @see org.alfresco.repo.cache.SimpleCache#put(K, V) + */ + public synchronized void put(K key, V value) + { + if (cache.size() < maxsize) + { + //if (log.isDebugEnabled()) + // log.debug("***Adding new key: " + key + " size: " + cache.size()); + cache.put(key, new CacheItem(key, value)); + } + } + + /** + * @see org.alfresco.repo.cache.SimpleCache#remove(K) + */ + public synchronized void remove(K key) + { + cache.remove(key); + } + + /** + * @see org.alfresco.repo.cache.SimpleCache#clear() + */ + public void clear() + { + // better to let the GC deal with removing the old map in one shot + // rather than try to clear each slot slowly using clear() + cache = new HashMap>(maxsize, 1.0f); + } + + /** + * @see org.alfresco.repo.cache.SimpleCache#contains(K) + */ + public synchronized boolean contains(K key) + { + return false; + } + + + /** + * Simple wrapper class for a cache item. + * Stores a timestamp of when the item was added so it can be purged from the cache later. + */ + private static class CacheItem + { + CacheItem(K key, V value) + { + this.key = key; + Value = value; + Timestamp = System.nanoTime(); + } + + public int hashCode() + { + return key.hashCode(); + } + + public boolean equals(Object o) + { + if (o instanceof CacheItem == false) return false; + return key.equals( ((CacheItem)o).key ); + } + + private K key; + long Timestamp; + V Value; + } +} diff --git a/source/java/org/alfresco/repo/ownable/impl/OwnableServiceImpl.java b/source/java/org/alfresco/repo/ownable/impl/OwnableServiceImpl.java index 7e1c10fcd5..781368e0e1 100644 --- a/source/java/org/alfresco/repo/ownable/impl/OwnableServiceImpl.java +++ b/source/java/org/alfresco/repo/ownable/impl/OwnableServiceImpl.java @@ -20,6 +20,7 @@ import java.io.Serializable; import java.util.HashMap; import org.alfresco.model.ContentModel; +import org.alfresco.repo.cache.AutoExpireCache; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; @@ -28,11 +29,18 @@ import org.alfresco.service.cmr.security.OwnableService; import org.alfresco.service.namespace.QName; import org.springframework.beans.factory.InitializingBean; +/** + * Ownership service support. Use in permissions framework as dynamic authority. + * + * @author Andy Hind + */ public class OwnableServiceImpl implements OwnableService, InitializingBean { private NodeService nodeService; private AuthenticationService authenticationService; + + private static AutoExpireCache ownerCache = new AutoExpireCache(1024, 0.75f); public OwnableServiceImpl() { @@ -40,7 +48,7 @@ public class OwnableServiceImpl implements OwnableService, InitializingBean } // IOC - + public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; @@ -54,11 +62,11 @@ public class OwnableServiceImpl implements OwnableService, InitializingBean public void afterPropertiesSet() throws Exception { - if(nodeService == null) + if (nodeService == null) { throw new IllegalArgumentException("A node service must be set"); } - if(authenticationService == null) + if (authenticationService == null) { throw new IllegalArgumentException("An authentication service must be set"); } @@ -66,28 +74,34 @@ public class OwnableServiceImpl implements OwnableService, InitializingBean // OwnableService implmentation - public String getOwner(NodeRef nodeRef) { - String userName = null; - // If ownership is not explicitly set then we fall back to the creator - // - if(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_OWNABLE)) + String userName = ownerCache.get(nodeRef); + + if (userName == null) { - userName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_OWNER)); - } - else if(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_AUDITABLE)) - { - userName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_CREATOR)); + // If ownership is not explicitly set then we fall back to the creator + if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_OWNABLE)) + { + userName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_OWNER)); + } + else if(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_AUDITABLE)) + { + userName = DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(nodeRef, ContentModel.PROP_CREATOR)); + } + ownerCache.put(nodeRef, userName); } + return userName; } public void setOwner(NodeRef nodeRef, String userName) { - if(!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_OWNABLE)) + ownerCache.remove(nodeRef); + + if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_OWNABLE)) { - HashMap properties = new HashMap(); + HashMap properties = new HashMap(1, 1.0f); properties.put(ContentModel.PROP_OWNER, userName); nodeService.addAspect(nodeRef, ContentModel.ASPECT_OWNABLE, properties); } @@ -95,7 +109,6 @@ public class OwnableServiceImpl implements OwnableService, InitializingBean { nodeService.setProperty(nodeRef, ContentModel.PROP_OWNER, userName); } - } public void takeOwnership(NodeRef nodeRef) @@ -107,5 +120,4 @@ public class OwnableServiceImpl implements OwnableService, InitializingBean { return getOwner(nodeRef) != null; } - } 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 28c29d759c..b8f0dd1334 100644 --- a/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java +++ b/source/java/org/alfresco/repo/security/permissions/impl/PermissionServiceImpl.java @@ -16,6 +16,7 @@ */ package org.alfresco.repo.security.permissions.impl; +import java.io.Serializable; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -28,6 +29,7 @@ import net.sf.acegisecurity.Authentication; import net.sf.acegisecurity.GrantedAuthority; import net.sf.acegisecurity.providers.dao.User; +import org.alfresco.repo.cache.AutoExpireCache; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.permissions.DynamicAuthority; import org.alfresco.repo.security.permissions.NodePermissionEntry; @@ -63,7 +65,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing private static Log log = LogFactory.getLog(PermissionServiceImpl.class); - private static AccessCache accessCache = new AccessCache(); + private static AutoExpireCache accessCache = new AutoExpireCache(4096, 0.75f); /* @@ -349,7 +351,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing // Use the smart authentication cache to improve permissions performance Authentication auth = authenticationComponent.getCurrentAuthentication(); Set authorisations = getAuthorisations(auth, nodeRef); - Object key = AccessCache.generateKey( + Serializable key = generateKey( authorisations, nodeRef, perm); @@ -396,6 +398,19 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing accessCache.put(key, status); return status; } + + /** + * 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 ref, PermissionReference perm) + { + HashSet key = new HashSet(auths); + key.add(ref.getId()); + key.add(perm.toString()); + return key; + } /** * Get the authorisations for the currently authenticated user @@ -564,112 +579,6 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing // SUPPORT CLASSES // - /** - * Cache to enhance performance of the permissions framework - * @author Kevin Roast - */ - private static class AccessCache - { - /** - * Return a permissions value from for the specified key - * - * @param key object @see generateKey() - * - * @return AccessStatus if found for the key - */ - synchronized AccessStatus get(Object key) - { - AccessCacheItem wrapper = cache.get(key); - if (wrapper != null) - { - // we cache values till a specific timeout then remove them - // this also gives other values a chance to get added if we are nearing the max size - if (cache.size() > (MAXSIZE * THRESHOLD) && - System.nanoTime() > (wrapper.Timestamp + TIMEDIFF)) - { - //if (log.isDebugEnabled()) - // log.debug("*****Removing timedout key: " + key); - cache.remove(key); - wrapper = null; - } - } - return wrapper != null ? wrapper.Status : null; - } - - /** - * Add an AccessStatus to the cache for the specified key - * - * @param key as a String @see generateKey() - * @param status AccessStatus to set for the key - */ - synchronized void put(Object key, AccessStatus status) - { - if (cache.size() < MAXSIZE) - { - //if (log.isDebugEnabled()) - // log.debug("***Adding new key: " + key + " size: " + cache.size()); - cache.put(key, new AccessCacheItem(key, status)); - } - } - - /** - * 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 Object generateKey(Set auths, NodeRef ref, PermissionReference perm) - { - Set key = new HashSet(auths); - key.add(ref.getId()); - key.add(perm.toString()); - - return key; - } - - void clear() - { - // better to let the GC deal with removing the old map in one shot - // rather than try to clear each slot slowly using clear() - cache = new HashMap(MAXSIZE, 1.0f); - } - - // TODO: configure these values via Spring - private final long TIMEDIFF = 1000000L * 1000L * 60L * 5L; // 5 mins in nano-seconds - private final int MAXSIZE = 4096; // maximum size of the cache - private final float THRESHOLD = 0.75f; // before we start removing items - private Map cache = new HashMap(MAXSIZE, 1.0f); - } - - /** - * Simple wrapper class for a cache item. - * Stores a timestamp of when the item was added so it can be purged from the cache later. - * @author Kevin Roast - */ - private static class AccessCacheItem - { - AccessCacheItem(Object key, AccessStatus status) - { - this.key = key; - Status = status; - Timestamp = System.nanoTime(); - } - - public int hashCode() - { - return key.hashCode(); - } - - public boolean equals(Object o) - { - if (o instanceof AccessCacheItem == false) return false; - return key.equals( ((AccessCacheItem)o).key ); - } - - private Object key; - long Timestamp; - AccessStatus Status; - } - /** * Support class to test the permission on a node. *