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 ba2a30b03d..b7cc5bb0d2 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 @@ -184,6 +184,11 @@ + + + + + @@ -1433,4 +1438,46 @@ order by childNode.id DESC + + + + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/domain/node/NodeDAO.java b/source/java/org/alfresco/repo/domain/node/NodeDAO.java index 4939ec6294..c880d10796 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeDAO.java +++ b/source/java/org/alfresco/repo/domain/node/NodeDAO.java @@ -702,6 +702,19 @@ public interface NodeDAO extends NodeBulkLoader final QName assocTypeQName, ChildAssocRefQueryCallback resultsCallback); + /** + * @param parentNodeId the parent node id + * @param minNodeId the minimum node ID (inclusive), null for no limitation on the minimum value of the node id + * @param maxNodeId the maximum node ID (exclusive), null for no limitation on the maximum value of the node id + * @param assocToExcludeTypeQNames the node associations to exclude, null for no filtering of the associations types + * @return list of child nodes + */ + public List selectChildAssocsWithoutNodeAssocsOfTypes( + final Long parentNodeId, + final Long minNodeId, + final Long maxNodeId, + final Set assocToExcludeTypeQNames); + /** * Finds the association between the node's primary parent and the node itself * @@ -873,6 +886,16 @@ public interface NodeDAO extends NodeBulkLoader */ public Long getMaxNodeId(); + /** + * Returns the [minId, maxId] interval for nodes of a type, with the transaction time in the given window time. + * + * @param type the node type + * @param startTxnTime the starting transaction time, null is allowed, case in which no minimum transaction time is considered + * @param endTxnTime the end transaction time, null is allowed, case in which no maximum transaction time is considered + * @return the interval, as a pair + */ + public Pair getNodeIdsIntervalForType(QName type, Long startTxnTime, Long endTxnTime); + /** * Select children by property values */ 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 073eead688..b38c13916e 100644 --- a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java @@ -114,6 +114,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl private static final String DELETE_NODE_PROPERTIES = "alfresco.node.delete_NodeProperties"; private static final String SELECT_NODE_MIN_ID = "alfresco.node.select_NodeMinId"; private static final String SELECT_NODE_MAX_ID = "alfresco.node.select_NodeMaxId"; + private static final String SELECT_NODE_INTERVAL_BY_TYPE = "alfresco.node.select_MinMaxNodeIdForNodeType"; private static final String SELECT_NODES_WITH_ASPECT_IDS = "alfresco.node.select_NodesWithAspectIds"; private static final String INSERT_NODE_ASSOC = "alfresco.node.insert.insert_NodeAssoc"; private static final String UPDATE_NODE_ASSOC = "alfresco.node.update_NodeAssoc"; @@ -138,6 +139,8 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl private static final String SELECT_CHILD_ASSOC_OF_PARENT_BY_NAME = "alfresco.node.select_ChildAssocOfParentByName"; private static final String SELECT_CHILD_ASSOCS_OF_PARENT_WITHOUT_PARENT_ASSOCS_OF_TYPE = "alfresco.node.select_ChildAssocsOfParentWithoutParentAssocsOfType"; + private static final String SELECT_CHILD_ASSOCS_OF_PARENT_WITHOUT_NODE_ASSOCS_OF_TYPE = + "alfresco.node.select_ChildAssocsOfParentWithoutNodeAssocsOfType"; private static final String SELECT_PARENT_ASSOCS_OF_CHILD = "alfresco.node.select_ParentAssocsOfChild"; private static final String UPDATE_PARENT_ASSOCS_OF_CHILD = "alfresco.node.update_ParentAssocsOfChild"; private static final String DELETE_SUBSCRIPTIONS = "alfresco.node.delete_NodeSubscriptions"; @@ -373,6 +376,38 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl return (Long) template.selectOne(SELECT_NODE_MAX_ID); } + @Override + public Pair getNodeIdsIntervalForType(QName type, Long startTxnTime, Long endTxnTime) + { + final Pair intervalPair = new Pair(LONG_ZERO, LONG_ZERO); + Pair typePair = qnameDAO.getQName(type); + if (typePair == null) + { + // Return default + return intervalPair; + } + TransactionQueryEntity txnQuery = new TransactionQueryEntity(); + txnQuery.setTypeQNameId(typePair.getFirst()); + txnQuery.setMinCommitTime(startTxnTime); + txnQuery.setMaxCommitTime(endTxnTime); + + ResultHandler resultHandler = new ResultHandler() + { + @SuppressWarnings("unchecked") + public void handleResult(ResultContext context) + { + Map result = (Map) context.getResultObject(); + if (result != null) + { + intervalPair.setFirst(result.get("minId")); + intervalPair.setSecond(result.get("maxId")); + } + } + }; + template.select(SELECT_NODE_INTERVAL_BY_TYPE, txnQuery, resultHandler); + return intervalPair; + } + @Override protected void updatePrimaryChildrenSharedAclId( Long txnId, @@ -1385,7 +1420,30 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl resultsCallback.done(); } - @SuppressWarnings("unchecked") + @Override + public List selectChildAssocsWithoutNodeAssocsOfTypes(Long parentNodeId, Long minNodeId, Long maxNodeId, Set assocTypeQNames) + { + IdsEntity idsEntity = new IdsEntity(); + + // Parent node id + Assert.notNull(parentNodeId, "The parent node id must not be null."); + idsEntity.setIdOne(parentNodeId); + // Node ids selection interval + idsEntity.setIdTwo(minNodeId); + idsEntity.setIdThree(maxNodeId); + // Associations types to exclude + if (assocTypeQNames != null) + { + Set childNodeTypeQNameIds = qnameDAO.convertQNamesToIds(assocTypeQNames, false); + if (childNodeTypeQNameIds.size() > 0) + { + idsEntity.setIds(new ArrayList(childNodeTypeQNameIds)); + } + } + + return template.selectList(SELECT_CHILD_ASSOCS_OF_PARENT_WITHOUT_NODE_ASSOCS_OF_TYPE, idsEntity); + } + @Override protected List selectPrimaryParentAssocs(Long childNodeId) { @@ -1665,7 +1723,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl if (deletedTypePair == null) { // Nothing to do - return 0L; + return LONG_ZERO; } TransactionQueryEntity txnQuery = new TransactionQueryEntity(); txnQuery.setTypeQNameId(deletedTypePair.getFirst()); diff --git a/source/test-java/org/alfresco/repo/domain/node/NodeDAOTest.java b/source/test-java/org/alfresco/repo/domain/node/NodeDAOTest.java index 646239013f..067b48ab9f 100644 --- a/source/test-java/org/alfresco/repo/domain/node/NodeDAOTest.java +++ b/source/test-java/org/alfresco/repo/domain/node/NodeDAOTest.java @@ -189,6 +189,79 @@ public class NodeDAOTest extends TestCase } } + public void testGetNodeIdsIntervalForType() throws Exception + { + // Different calls with equivalent parameters should return the same values + Pair interval1 = getNodeIdsInterval(0L, System.currentTimeMillis()); + Pair interval2 = getNodeIdsInterval(null, System.currentTimeMillis()); + Pair interval3 = getNodeIdsInterval(null, null); + + assertEquals(interval1.getFirst(), interval2.getFirst()); + assertEquals(interval2.getFirst(), interval3.getFirst()); + + assertEquals(interval1.getSecond(), interval2.getSecond()); + assertEquals(interval2.getSecond(), interval3.getSecond()); + } + + private Pair getNodeIdsInterval(final Long minTxnTime, final Long maxTxnTime) + { + RetryingTransactionCallback> callback = new RetryingTransactionCallback>() + { + public Pair execute() throws Throwable + { + return nodeDAO.getNodeIdsIntervalForType(ContentModel.TYPE_FOLDER, minTxnTime, maxTxnTime); + } + }; + + return txnHelper.doInTransaction(callback, true); + } + + public void testSelectChildAssocsWithoutNodeAssocsOfTypes() + { + final StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + final Long parentId = nodeDAO.getRootNode(storeRef).getFirst(); + + final AtomicLong min = new AtomicLong(0L); + final AtomicLong max = new AtomicLong(0L); + final Set typeAssocsToExclude = Collections.singleton(QName.createQName("noType")); + + RetryingTransactionCallback> callback = new RetryingTransactionCallback>() + { + public List execute() throws Throwable + { + long minNodeId = min.get(); + long maxNodeId = max.get(); + return nodeDAO.selectChildAssocsWithoutNodeAssocsOfTypes(parentId, minNodeId, maxNodeId, typeAssocsToExclude); + } + }; + + // Get the current node range + Pair nodeRange = getNodeIdsInterval(0L, System.currentTimeMillis()); + + Long minNodeId = nodeRange.getFirst(); + Long maxNodeId = nodeRange.getSecond(); + + min.set(minNodeId.longValue()); + + // Iterate across the nodes in the [minNodeId, maxNodeId] interval + while (min.longValue() <= maxNodeId.longValue()) + { + max.set(min.get() + 100L); // 100 increments + // Get the nodes + List nodes = txnHelper.doInTransaction(callback, true); + for (Node node : nodes) + { + Long nodeId = node.getId(); + assertNotNull(nodeId); + assertTrue("the min should be inclusive.", min.longValue() <= nodeId.longValue()); + assertTrue("the max should be exclusive.", max.longValue() > nodeId.longValue()); + } + + // Shift the window up + min.set(max.get()); + } + } + public void testGetNodesWithAspects() throws Throwable { final NodeRefQueryCallback callback = new NodeRefQueryCallback()