Rework APIs of new getChildAssocs with result count limit

- Use regular pattern of get -> select in the DAO
 - All getChildren* batching done after the query
 - Unit tests
 - In progress: Added option (low level select only) to constrain by assoc ID


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@31318 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2011-10-18 13:48:32 +00:00
parent 00ccf994bd
commit 7e27fbd5d8
7 changed files with 117 additions and 59 deletions

View File

@@ -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<NodeRef> nodeRefs;
@@ -2935,18 +2934,10 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO
Pair<Long, NodeRef> parentNodePair,
Pair<Long, NodeRef> 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<QName> 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<QName> assocTypeQNames,

View File

@@ -185,7 +185,7 @@ public class ChildAssocEntity
*/
public ChildAssocEntity()
{
ordered = true;
ordered = false;
}
@Override

View File

@@ -503,15 +503,15 @@ public interface NodeDAO extends NodeBulkLoader
* @param assocQName the association qname to filter on; <tt>null</tt> 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<ChildAssociationRef> 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 <tt>QName</tt>.

View File

@@ -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<ChildAssociationRef> 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<ChildAssociationRef> result = new LinkedList<ChildAssociationRef>();
final List<NodeRef> toLoad = new LinkedList<NodeRef>();
// 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<Long, ChildAssociationRef> childAssocPair, Pair<Long, NodeRef> parentNodePair,
Pair<Long, NodeRef> 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

View File

@@ -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<Void> outerCallback = new RetryingTransactionCallback<Void>()
{
@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<ChildAssociationRef> 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<ChildAssociationRef> 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()
{

View File

@@ -1707,8 +1707,33 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
{
// Get the node
Pair<Long, NodeRef> nodePair = getNodePairNotNull(nodeRef);
// We have a callback handler to filter results
final List<ChildAssociationRef> results = new ArrayList<ChildAssociationRef>(10);
ChildAssocRefQueryCallback callback = new ChildAssocRefQueryCallback()
{
public boolean preLoadNodes()
{
return preload;
}
public boolean handle(
Pair<Long, ChildAssociationRef> childAssocPair,
Pair<Long, NodeRef> parentNodePair,
Pair<Long, NodeRef> 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<ChildAssociationRef> getChildAssocs(NodeRef nodeRef, Set<QName> childNodeTypeQNames)