From 1dca4cd1fca66622c33ba7ead1b8c046086c6481 Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Sat, 22 Oct 2011 05:06:15 +0000 Subject: [PATCH] Fixed ALF-10964: Add back cache for getChildByName - Originally removed as part of the 'reverse lookup' of parentAssocsCache - This cache is NOT clustered; the child target version is checked; requery if necessary - NB: Cache misses are NOT cached. Do do so would mean making the cache clustered. It is better to avoid querying for random files that don't exist over and over. Add a higher level cache (as is done in CIFS) for that case. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@31417 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/cache-context.xml | 33 +++++++++ config/alfresco/dao/dao-context.xml | 1 + config/alfresco/ehcache-default.xml | 10 +++ .../ehcache-custom.xml.sample.cluster | 10 +++ .../repo/domain/node/AbstractNodeDAOImpl.java | 72 ++++++++++++++++++- .../alfresco/repo/node/NodeServiceTest.java | 35 +++++++++ 6 files changed, 159 insertions(+), 2 deletions(-) diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml index c1bcea9a32..41c9341ae1 100644 --- a/config/alfresco/cache-context.xml +++ b/config/alfresco/cache-context.xml @@ -309,6 +309,39 @@ + + + + + + + + + + + + + + org.alfresco.cache.node.childByNameCache + + + + + + + + + + + + + org.alfresco.cache.node.childByNameTransactionalCache + + + + + + diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml index 1c0edc83f9..87c53182ca 100644 --- a/config/alfresco/dao/dao-context.xml +++ b/config/alfresco/dao/dao-context.xml @@ -120,6 +120,7 @@ + diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index 9890c62d23..183444854a 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -35,6 +35,9 @@ overflowToDisk="false" statistics="false" /> + + + + diff --git a/config/alfresco/extension/ehcache-custom.xml.sample.cluster b/config/alfresco/extension/ehcache-custom.xml.sample.cluster index d2cde19763..3964c019eb 100644 --- a/config/alfresco/extension/ehcache-custom.xml.sample.cluster +++ b/config/alfresco/extension/ehcache-custom.xml.sample.cluster @@ -197,6 +197,16 @@ replicateAsynchronously = false"/> + + + + parentAssocsCache; + /** + * Cache for fast lookups of child nodes by cm:name. + */ + private SimpleCache childByNameCache; + /** * Constructor. Set up various instance-specific members such as caches and locks. */ @@ -187,6 +193,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO aspectsCache = new EntityLookupCache, Serializable>(new AspectsCallbackDAO()); propertiesCache = new EntityLookupCache, Serializable>(new PropertiesCallbackDAO()); parentAssocsCache = new EntityLookupCache(new ParentAssocsCallbackDAO()); + childByNameCache = new NullCache(); } /** @@ -345,7 +352,17 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO CACHE_REGION_PARENT_ASSOCS, new ParentAssocsCallbackDAO()); } - + + /** + * Set the cache that maintains lookups by child cm:name + * + * @param childByNameCache the cache + */ + public void setChildByNameCache(SimpleCache childByNameCache) + { + this.childByNameCache = childByNameCache; + } + /* * Initialize */ @@ -2018,6 +2035,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO // Touch to bring into current transaction if (updated) { + // We have to explicitly update the node (sys:locale or cm:auditable) updateNodeImpl(node, nodeUpdate); } @@ -3023,9 +3041,59 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO } } + /** + * Checks a cache and then queries. + *

+ * Note: If we were to cach misses, then we would have to ensure that the cache is + * kept up to date whenever any affection association is changed. This is actually + * not possible without forcing the cache to be fully clustered. So to + * avoid clustering the cache, we instead watch the node child version, + * which relies on a cache that is already clustered. + */ public Pair getChildAssoc(Long parentNodeId, QName assocTypeQName, String childName) { - ChildAssocEntity assoc = selectChildAssoc(parentNodeId, assocTypeQName, childName); + ChildByNameKey key = new ChildByNameKey(parentNodeId, assocTypeQName, childName); + ChildAssocEntity assoc = childByNameCache.get(key); + boolean query = false; + if (assoc == null) + { + query = true; + } + else + { + // Check that the resultant child node has not moved on + Node childNode = assoc.getChildNode(); + Long childNodeId = childNode.getId(); + NodeVersionKey childNodeVersionKey = childNode.getNodeVersionKey(); + Pair childNodeFromCache = nodesCache.getByKey(childNodeId); + if (childNodeFromCache == null) + { + // Child node no longer exists (or never did) + query = true; + } + else + { + NodeVersionKey childNodeFromCacheVersionKey = childNodeFromCache.getSecond().getNodeVersionKey(); + if (!childNodeFromCacheVersionKey.equals(childNodeVersionKey)) + { + // The child node has moved on. We don't know why, but must query again. + query = true; + } + } + } + if (query) + { + assoc = selectChildAssoc(parentNodeId, assocTypeQName, childName); + if (assoc != null) + { + childByNameCache.put(key, assoc); + } + else + { + // We do not cache misses. See javadoc. + } + } + // Now return, checking the assoc's ID for null return assoc == null ? null : assoc.getPair(qnameDAO); } diff --git a/source/java/org/alfresco/repo/node/NodeServiceTest.java b/source/java/org/alfresco/repo/node/NodeServiceTest.java index bc2039112b..75aa615128 100644 --- a/source/java/org/alfresco/repo/node/NodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/NodeServiceTest.java @@ -500,4 +500,39 @@ public class NodeServiceTest extends TestCase List parentAssocsPost = nodeService.getParentAssocs(secondaryNodeRef); assertEquals("Incorrect number of parent assocs", 1, parentAssocsPost.size()); } + + /** + * Checks that file renames are handled when getting children + */ + public void testCaches_RenameNode() + { + final NodeRef[] nodeRefs = new NodeRef[2]; + final NodeRef workspaceRootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); + buildNodeHierarchy(workspaceRootNodeRef, nodeRefs); + + // What is the name of the first child? + String name = (String) nodeService.getProperty(nodeRefs[1], ContentModel.PROP_NAME); + // Now query for it + NodeRef nodeRefCheck = nodeService.getChildByName(nodeRefs[0], ContentModel.ASSOC_CONTAINS, name); + assertNotNull("Did not find node by name", nodeRefCheck); + assertEquals("Node found was not correct", nodeRefs[1], nodeRefCheck); + + // Rename the node + nodeService.setProperty(nodeRefs[1], ContentModel.PROP_NAME, "New Name"); + // Should find nothing + nodeRefCheck = nodeService.getChildByName(nodeRefs[0], ContentModel.ASSOC_CONTAINS, name); + assertNull("Should not have found anything", nodeRefCheck); + + // Add another child with the same original name + NodeRef newChildNodeRef = nodeService.createNode( + nodeRefs[0], + ContentModel.ASSOC_CONTAINS, + QName.createQName(NAMESPACE, name), + ContentModel.TYPE_FOLDER, + Collections.singletonMap(ContentModel.PROP_NAME, (Serializable)name)).getChildRef(); + // We should find this new node when looking for the name + nodeRefCheck = nodeService.getChildByName(nodeRefs[0], ContentModel.ASSOC_CONTAINS, name); + assertNotNull("Did not find node by name", nodeRefCheck); + assertEquals("Node found was not correct", newChildNodeRef, nodeRefCheck); + } }