diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml index 7aaf7e6327..f636838e14 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/node-common-SqlMap.xml @@ -917,6 +917,7 @@ where parentNode.id = #{parentNode.id} + = #{id}]]> and assoc.child_node_id = #{childNode.id} and assoc.type_qname_id in diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index a7416490da..e5849c9efb 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -55,9 +55,9 @@ import org.alfresco.repo.domain.usage.UsageDAO; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.security.permissions.AccessControlListProperties; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.TransactionAwareSingleton; import org.alfresco.repo.transaction.TransactionListenerAdapter; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.InvalidTypeException; @@ -71,20 +71,20 @@ import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.InvalidStoreRefException; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeRef.Status; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.repository.NodeRef.Status; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.ReadOnlyServerException; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.EqualsHelper; +import org.alfresco.util.EqualsHelper.MapValueComparison; import org.alfresco.util.GUID; import org.alfresco.util.Pair; import org.alfresco.util.PropertyCheck; import org.alfresco.util.ReadWriteLockExecuter; import org.alfresco.util.SerializationUtils; -import org.alfresco.util.EqualsHelper.MapValueComparison; import org.apache.commons.lang.mutable.MutableInt; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -2900,7 +2900,6 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO */ private class ChildAssocRefBatchingQueryCallback implements ChildAssocRefQueryCallback { - private static final int BATCH_SIZE = 256 * 4; private final ChildAssocRefQueryCallback callback; private final boolean preload; private final List nodeRefs; @@ -2935,18 +2934,10 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO Pair parentNodePair, Pair childNodePair) { - if (!preload) + if (preload) { - return callback.handle(childAssocPair, parentNodePair, childNodePair); + nodeRefs.add(childNodePair.getSecond()); } - // Batch it - if (nodeRefs.size() >= BATCH_SIZE) - { - cacheNodes(nodeRefs); - nodeRefs.clear(); - } - nodeRefs.add(childNodePair.getSecond()); - return callback.handle(childAssocPair, parentNodePair, childNodePair); } public void done() @@ -2957,7 +2948,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO cacheNodes(nodeRefs); nodeRefs.clear(); } - + // Done callback.done(); } } @@ -2977,6 +2968,23 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO new ChildAssocRefBatchingQueryCallback(resultsCallback)); } + @Override + public void getChildAssocs( + Long parentNodeId, + QName assocTypeQName, + QName assocQName, + int maxResults, + ChildAssocRefQueryCallback resultsCallback) + { + selectChildAssocs( + parentNodeId, + assocTypeQName, + assocQName, + null, + maxResults, + new ChildAssocRefBatchingQueryCallback(resultsCallback)); + } + public void getChildAssocs(Long parentNodeId, Set assocTypeQNames, ChildAssocRefQueryCallback resultsCallback) { switch (assocTypeQNames.size()) @@ -3926,6 +3934,13 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO Boolean isPrimary, Boolean sameStore, ChildAssocRefQueryCallback resultsCallback); + protected abstract void selectChildAssocs( + Long parentNodeId, + QName assocTypeQName, + QName assocQName, + Long minAssocIdInclusive, + int maxResults, + ChildAssocRefQueryCallback resultsCallback); protected abstract void selectChildAssocs( Long parentNodeId, Set assocTypeQNames, diff --git a/source/java/org/alfresco/repo/domain/node/ChildAssocEntity.java b/source/java/org/alfresco/repo/domain/node/ChildAssocEntity.java index b13dfedf1e..3d56a600cc 100644 --- a/source/java/org/alfresco/repo/domain/node/ChildAssocEntity.java +++ b/source/java/org/alfresco/repo/domain/node/ChildAssocEntity.java @@ -185,7 +185,7 @@ public class ChildAssocEntity */ public ChildAssocEntity() { - ordered = true; + ordered = false; } @Override diff --git a/source/java/org/alfresco/repo/domain/node/NodeDAO.java b/source/java/org/alfresco/repo/domain/node/NodeDAO.java index 1096fe3dfc..fe9194efa0 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeDAO.java +++ b/source/java/org/alfresco/repo/domain/node/NodeDAO.java @@ -503,15 +503,15 @@ public interface NodeDAO extends NodeBulkLoader * @param assocQName the association qname to filter on; null for no filtering * @param maxResults the maximum number of results to return. The query will be terminated efficiently * after that number of results - * @param preload should the child nodes be batch loaded? + * @param resultsCallback the callback that will be called with the results * @return a list of child associations */ - public List getChildAssocs( + public void getChildAssocs( Long parentNodeId, QName assocTypeQName, QName assocQName, final int maxResults, - boolean preload); + ChildAssocRefQueryCallback resultsCallback); /** * Get the child associations of a given parent node, optionally filtering on type QName. diff --git a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java index da202758a6..d681912e4b 100644 --- a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java @@ -23,7 +23,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -1014,6 +1013,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl { if (!assoc.setTypeQNameAll(qnameDAO, assocTypeQName, false)) { + resultsCallback.done(); return; // Shortcut } } @@ -1022,6 +1022,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl { if (!assoc.setQNameAll(qnameDAO, assocQName, false)) { + resultsCallback.done(); return; // Shortcut } } @@ -1045,25 +1046,29 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl resultsCallback.done(); } - public List getChildAssocs( + @Override + public void selectChildAssocs( Long parentNodeId, QName assocTypeQName, QName assocQName, + Long minAssocIdInclusive, final int maxResults, - boolean preload) + ChildAssocRefQueryCallback resultsCallback) { ChildAssocEntity assoc = new ChildAssocEntity(); // Parent NodeEntity parentNode = new NodeEntity(); parentNode.setId(parentNodeId); assoc.setParentNode(parentNode); + assoc.setId(minAssocIdInclusive); // Type QName if (assocTypeQName != null) { if (!assoc.setTypeQNameAll(qnameDAO, assocTypeQName, false)) { - return Collections.emptyList(); // Shortcut + resultsCallback.done(); + return; // Shortcut } } // QName @@ -1071,49 +1076,23 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl { if (!assoc.setQNameAll(qnameDAO, assocQName, false)) { - return Collections.emptyList(); // Shortcut + resultsCallback.done(); + return; // Shortcut } } - final List result = new LinkedList(); - final List toLoad = new LinkedList(); - - // We can't invoke the row handler whilst the limited query is running as it's illegal on some databases (MySQL) - List entities = template.selectList(SELECT_CHILD_ASSOCS_OF_PARENT_LIMITED, assoc, new RowBounds(0, - maxResults)); - ChildAssocResultHandler rowHandler = new ChildAssocResultHandler(new ChildAssocRefQueryCallback(){ - - @Override - public boolean handle(Pair childAssocPair, Pair parentNodePair, - Pair childNodePair) - { - result.add(childAssocPair.getSecond()); - toLoad.add(childNodePair.getSecond()); - return true; - } - - @Override - public void done() - { - } - - @Override - public boolean preLoadNodes() - { - return false; - }}); + ChildAssocResultHandler resultHandler = new ChildAssocResultHandler(resultsCallback); + + RowBounds rowBounds = new RowBounds(0, maxResults); + List entities = template.selectList(SELECT_CHILD_ASSOCS_OF_PARENT_LIMITED, assoc, rowBounds); final DefaultResultContext resultContext = new DefaultResultContext(); for (Object entity : entities) { resultContext.nextResultObject(entity); - rowHandler.handleResult(resultContext); - } - if (preload && !toLoad.isEmpty()) - { - cacheNodes(toLoad); + resultHandler.handleResult(resultContext); } - return result; + resultsCallback.done(); } @Override diff --git a/source/java/org/alfresco/repo/node/NodeServiceTest.java b/source/java/org/alfresco/repo/node/NodeServiceTest.java index 441373a55b..bc2039112b 100644 --- a/source/java/org/alfresco/repo/node/NodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/NodeServiceTest.java @@ -241,11 +241,16 @@ public class NodeServiceTest extends TestCase public void testConcurrentArchive() throws Exception { final NodeRef workspaceRootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); + final NodeRef[] nodesPrimer = new NodeRef[1]; + buildNodeHierarchy(workspaceRootNodeRef, nodesPrimer); final NodeRef[] nodesOne = new NodeRef[10]; buildNodeHierarchy(workspaceRootNodeRef, nodesOne); final NodeRef[] nodesTwo = new NodeRef[10]; buildNodeHierarchy(workspaceRootNodeRef, nodesTwo); + // Prime the root of the archive store (first child adds inherited ACL) + nodeService.deleteNode(nodesPrimer[0]); + RetryingTransactionCallback outerCallback = new RetryingTransactionCallback() { @Override @@ -426,8 +431,41 @@ public class NodeServiceTest extends TestCase } } + public void testGetChildren_Limited() + { + // Create a node and loads of children + final NodeRef[] liveNodeRefs = new NodeRef[10]; + final NodeRef workspaceRootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); + + buildNodeHierarchy(workspaceRootNodeRef, liveNodeRefs); + + // Hook 3rd and subsequent children into 1st child + for (int i = 2; i < liveNodeRefs.length; i++) + { + nodeService.addChild( + liveNodeRefs[0], + liveNodeRefs[i], + ContentModel.ASSOC_CONTAINS, + QName.createQName(NAMESPACE, "secondary")); + } + + // Do limited queries each time + for (int i = 1; i < liveNodeRefs.length; i++) + { + List childAssocRefs = nodeService.getChildAssocs(liveNodeRefs[0], null, null, i, true); + assertEquals("Expected exact number of child assocs", i, childAssocRefs.size()); + } + + // Repeat, but don't preload + for (int i = 1; i < liveNodeRefs.length; i++) + { + List childAssocRefs = nodeService.getChildAssocs(liveNodeRefs[0], null, null, i, false); + assertEquals("Expected exact number of child assocs", i, childAssocRefs.size()); + } + } + /** - * Checks that the node caches react correct when a node is deleted + * Checks that the node caches react correctly when a node is deleted */ public void testCaches_DeleteNode() { diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index 3b36c93427..c69e6408a4 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -1707,8 +1707,33 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl { // Get the node Pair nodePair = getNodePairNotNull(nodeRef); + + // We have a callback handler to filter results + final List results = new ArrayList(10); + ChildAssocRefQueryCallback callback = new ChildAssocRefQueryCallback() + { + public boolean preLoadNodes() + { + return preload; + } + + public boolean handle( + Pair childAssocPair, + Pair parentNodePair, + Pair childNodePair) + { + results.add(childAssocPair.getSecond()); + return true; + } + + public void done() + { + } + }; // Get the assocs pointing to it - return nodeDAO.getChildAssocs(nodePair.getFirst(), typeQName, qname, maxResults, preload); + nodeDAO.getChildAssocs(nodePair.getFirst(), typeQName, qname, maxResults, callback); + // Done + return results; } public List getChildAssocs(NodeRef nodeRef, Set childNodeTypeQNames)