From eb64645c972be96b99d87a738ac5460569b925d1 Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Thu, 9 Jun 2011 14:19:10 +0000 Subject: [PATCH] Merged DEV/SWIFT to HEAD 27584: ALF-8189: RINF 16: Upgrade peer associations - Create scripts and upgrades to add alf_node_assoc.assoc_index for all DBs - Part of ALF-7404: RINF 16: Peer association enhancements 27640: Re-added changes from rev 27125, which were overwritten by 27584 (ALF-8334: RSOLR 013) 28295: (RECORD ONLY) Upgrade Tika and POI to the latest versions git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@28309 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/bootstrap-context.xml | 1 + .../AlfrescoCreate-RepoTables.sql | 5 +- .../AlfrescoCreate-RepoTables.sql | 5 +- .../NodeAssoc-Ordering.sql | 68 +++++++++++++++ .../NodeAssoc-Ordering.sql | 70 ++++++++++++++++ .../node-common-SqlMap.xml | 25 +++++- .../alfresco/patch/patch-services-context.xml | 11 +++ config/alfresco/version.properties | 2 +- .../org/alfresco/repo/avm/AVMNodeService.java | 10 +-- .../repo/domain/node/AbstractNodeDAOImpl.java | 84 ++++++++++++++++--- .../repo/domain/node/NodeAssocEntity.java | 12 +++ .../alfresco/repo/domain/node/NodeDAO.java | 21 +++-- .../repo/domain/node/ibatis/NodeDAOImpl.java | 28 ++++++- .../repo/node/AbstractNodeServiceImpl.java | 8 ++ .../repo/node/BaseNodeServiceTest.java | 84 ++++++++++++++++--- .../repo/node/db/DbNodeServiceImpl.java | 41 ++++++--- .../repo/version/NodeServiceImpl.java | 10 +++ 17 files changed, 425 insertions(+), 60 deletions(-) create mode 100644 config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/NodeAssoc-Ordering.sql create mode 100644 config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/NodeAssoc-Ordering.sql diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 9b94e9dfda..422ea4a933 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -140,6 +140,7 @@ + diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-RepoTables.sql b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-RepoTables.sql index 80738bf278..7fe6f2a93c 100644 --- a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-RepoTables.sql +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/AlfrescoCreate-RepoTables.sql @@ -272,10 +272,11 @@ CREATE TABLE alf_node_assoc source_node_id BIGINT NOT NULL, target_node_id BIGINT NOT NULL, type_qname_id BIGINT NOT NULL, + assoc_index BIGINT NOT NULL, PRIMARY KEY (id), UNIQUE KEY source_node_id (source_node_id, target_node_id, type_qname_id), - KEY fk_alf_nass_snode (source_node_id), - KEY fk_alf_nass_tnode (target_node_id), + KEY fk_alf_nass_snode (source_node_id, type_qname_id, assoc_index), + KEY fk_alf_nass_tnode (target_node_id, type_qname_id), KEY fk_alf_nass_tqn (type_qname_id), CONSTRAINT fk_alf_nass_snode FOREIGN KEY (source_node_id) REFERENCES alf_node (id), CONSTRAINT fk_alf_nass_tnode FOREIGN KEY (target_node_id) REFERENCES alf_node (id), diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-RepoTables.sql b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-RepoTables.sql index cbcebf639d..5b04d17daf 100644 --- a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-RepoTables.sql +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/AlfrescoCreate-RepoTables.sql @@ -290,14 +290,15 @@ CREATE TABLE alf_node_assoc source_node_id INT8 NOT NULL, target_node_id INT8 NOT NULL, type_qname_id INT8 NOT NULL, + assoc_index INT8 NOT NULL, PRIMARY KEY (id), CONSTRAINT fk_alf_nass_snode FOREIGN KEY (source_node_id) REFERENCES alf_node (id), CONSTRAINT fk_alf_nass_tnode FOREIGN KEY (target_node_id) REFERENCES alf_node (id), CONSTRAINT fk_alf_nass_tqn FOREIGN KEY (type_qname_id) REFERENCES alf_qname (id) ); CREATE UNIQUE INDEX source_node_id ON alf_node_assoc (source_node_id, target_node_id, type_qname_id); -CREATE INDEX fk_alf_nass_snode ON alf_node_assoc (source_node_id); -CREATE INDEX fk_alf_nass_tnode ON alf_node_assoc (target_node_id); +CREATE INDEX fk_alf_nass_snode ON alf_node_assoc (source_node_id, type_qname_id, assoc_index); +CREATE INDEX fk_alf_nass_tnode ON alf_node_assoc (target_node_id, type_qname_id); CREATE INDEX fk_alf_nass_tqn ON alf_node_assoc (type_qname_id); CREATE TABLE alf_node_properties diff --git a/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/NodeAssoc-Ordering.sql b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/NodeAssoc-Ordering.sql new file mode 100644 index 0000000000..d37596e552 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.MySQLInnoDBDialect/NodeAssoc-Ordering.sql @@ -0,0 +1,68 @@ +-- +-- Title: Add 'assoc_index' column to 'alf_node_assoc' +-- Database: MySQL +-- Since: V4.0 Schema 5008 +-- Author: Derek Hulley +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +-- Cut the original table to just the data +ALTER TABLE alf_node_assoc + DROP FOREIGN KEY fk_alf_nass_snode, + DROP FOREIGN KEY fk_alf_nass_tnode, + DROP FOREIGN KEY fk_alf_nass_tqn, + DROP INDEX source_node_id, + DROP INDEX fk_alf_nass_snode, + DROP INDEX fk_alf_nass_tnode, + DROP INDEX fk_alf_nass_tqn; +ALTER TABLE alf_node_assoc + RENAME TO t_alf_node_assoc; + +-- So now it's just raw data +-- Reconstruct the table +CREATE TABLE alf_node_assoc +( + id BIGINT NOT NULL AUTO_INCREMENT, + version BIGINT NOT NULL, + source_node_id BIGINT NOT NULL, + target_node_id BIGINT NOT NULL, + type_qname_id BIGINT NOT NULL, + assoc_index BIGINT NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY source_node_id (source_node_id, target_node_id, type_qname_id), + KEY fk_alf_nass_snode (source_node_id, type_qname_id, assoc_index), + KEY fk_alf_nass_tnode (target_node_id, type_qname_id), + KEY fk_alf_nass_tqn (type_qname_id), + CONSTRAINT fk_alf_nass_snode FOREIGN KEY (source_node_id) REFERENCES alf_node (id), + CONSTRAINT fk_alf_nass_tnode FOREIGN KEY (target_node_id) REFERENCES alf_node (id), + CONSTRAINT fk_alf_nass_tqn FOREIGN KEY (type_qname_id) REFERENCES alf_qname (id) +) ENGINE=InnoDB; + +-- Copy the data over +--FOREACH t_alf_node_assoc.id system.upgrade.alf_node_assoc.batchsize +INSERT INTO alf_node_assoc + (id, version, source_node_id, target_node_id, type_qname_id, assoc_index) + ( + SELECT + id, 1, source_node_id, target_node_id, type_qname_id, 1 + FROM + t_alf_node_assoc + WHERE + id >= ${LOWERBOUND} AND id <= ${UPPERBOUND} + ); + +-- Drop old data +DROP TABLE t_alf_node_assoc; + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-NodeAssoc-Ordering'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V4.0-NodeAssoc-Ordering', 'Manually executed script upgrade V4.0: Add assoc_index column to alf_node_assoc', + 0, 5008, -1, 5009, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/NodeAssoc-Ordering.sql b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/NodeAssoc-Ordering.sql new file mode 100644 index 0000000000..a533ebde41 --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/4.0/org.hibernate.dialect.PostgreSQLDialect/NodeAssoc-Ordering.sql @@ -0,0 +1,70 @@ +-- +-- Title: Add 'assoc_index' column to 'alf_node_assoc' +-- Database: PostgreSQL +-- Since: V4.0 Schema 5008 +-- Author: Derek Hulley +-- +-- Please contact support@alfresco.com if you need assistance with the upgrade. +-- + +-- Cut the original table to just the data +DROP INDEX source_node_id; --(optional) +DROP INDEX alf_node_assoc_source_node_id_key; --(optional) +DROP INDEX fk_alf_nass_snode; +DROP INDEX fk_alf_nass_tnode; +DROP INDEX fk_alf_nass_tqn; +ALTER TABLE alf_node_assoc + DROP CONSTRAINT fk_alf_nass_snode; +ALTER TABLE alf_node_assoc + DROP CONSTRAINT fk_alf_nass_tnode; +ALTER TABLE alf_node_assoc + DROP CONSTRAINT fk_alf_nass_tqn; +ALTER TABLE alf_node_assoc RENAME TO t_alf_node_assoc; + +-- So now it's just raw data +-- Reconstruct the table (leave the sequence as is) +CREATE TABLE alf_node_assoc +( + id INT8 NOT NULL, + version INT8 NOT NULL, + source_node_id INT8 NOT NULL, + target_node_id INT8 NOT NULL, + type_qname_id INT8 NOT NULL, + assoc_index INT8 NOT NULL, + PRIMARY KEY (id), + CONSTRAINT fk_alf_nass_snode FOREIGN KEY (source_node_id) REFERENCES alf_node (id), + CONSTRAINT fk_alf_nass_tnode FOREIGN KEY (target_node_id) REFERENCES alf_node (id), + CONSTRAINT fk_alf_nass_tqn FOREIGN KEY (type_qname_id) REFERENCES alf_qname (id) +); +CREATE UNIQUE INDEX source_node_id ON alf_node_assoc (source_node_id, target_node_id, type_qname_id); +CREATE INDEX fk_alf_nass_snode ON alf_node_assoc (source_node_id, type_qname_id, assoc_index); +CREATE INDEX fk_alf_nass_tnode ON alf_node_assoc (target_node_id, type_qname_id); +CREATE INDEX fk_alf_nass_tqn ON alf_node_assoc (type_qname_id); + +-- Copy the data over +--FOREACH t_alf_node_assoc.id system.upgrade.alf_node_assoc.batchsize +INSERT INTO alf_node_assoc + (id, version, source_node_id, target_node_id, type_qname_id, assoc_index) + ( + SELECT + id, 1, source_node_id, target_node_id, type_qname_id, 1 + FROM + t_alf_node_assoc + WHERE + id >= ${LOWERBOUND} AND id <= ${UPPERBOUND} + ); + +-- Drop old data +DROP TABLE t_alf_node_assoc; + +-- +-- Record script finish +-- +DELETE FROM alf_applied_patch WHERE id = 'patch.db-V4.0-NodeAssoc-Ordering'; +INSERT INTO alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + VALUES + ( + 'patch.db-V4.0-NodeAssoc-Ordering', 'Manually executed script upgrade V4.0: Add assoc_index column to alf_node_assoc', + 0, 5008, -1, 5009, null, 'UNKNOWN', ${TRUE}, ${TRUE}, 'Script completed' + ); \ No newline at end of file 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 1b35589795..d6fcaaa258 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 @@ -111,6 +111,7 @@ + @@ -269,12 +270,12 @@ - insert into alf_node_assoc (version, source_node_id, target_node_id, type_qname_id) - values (#{version}, #{sourceNode.id}, #{targetNode.id}, #{typeQNameId}) + insert into alf_node_assoc (version, source_node_id, target_node_id, type_qname_id, assoc_index) + values (#{version}, #{sourceNode.id}, #{targetNode.id}, #{typeQNameId}, #{assocIndex}) - insert into alf_node_assoc (id, version, source_node_id, target_node_id, type_qname_id) - values (#{id}, #{version}, #{sourceNode.id}, #{targetNode.id}, #{typeQNameId}) + insert into alf_node_assoc (id, version, source_node_id, target_node_id, type_qname_id, assoc_index) + values (#{id}, #{version}, #{sourceNode.id}, #{targetNode.id}, #{typeQNameId}, #{assocIndex}) @@ -725,12 +726,18 @@ where sourceNode.id = #{sourceNode.id} + + + + + select diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 21378dc101..56d8e8b214 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -2849,4 +2849,15 @@ + + + + + + + + classpath:alfresco/dbscripts/upgrade/4.0/${db.script.dialect}/NodeAssoc-Ordering.sql + + + diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index 5db20260fb..c1358868da 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=5008 +version.schema=5009 diff --git a/source/java/org/alfresco/repo/avm/AVMNodeService.java b/source/java/org/alfresco/repo/avm/AVMNodeService.java index ea16e50ff6..7a589fc8c0 100644 --- a/source/java/org/alfresco/repo/avm/AVMNodeService.java +++ b/source/java/org/alfresco/repo/avm/AVMNodeService.java @@ -1815,15 +1815,9 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi } /** - * - * @param sourceRef a reference to a real node - * @param targetRef a reference to a node - * @param assocTypeQName the qualified name of the association type - * @return Returns a reference to the new association - * @throws InvalidNodeRefException if either of the nodes could not be found - * @throws AssociationExistsException + * @throws UnsupportedOperationException always */ - public AssociationRef createAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) + public AssociationRef createAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName, Long insertAfter) throws InvalidNodeRefException, AssociationExistsException { throw new UnsupportedOperationException("AVM does not support arbitrary associations."); diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index 18e3d6931a..7367473c92 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -2304,20 +2304,53 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO * Node assocs */ - public Long newNodeAssoc(Long sourceNodeId, Long targetNodeId, QName assocTypeQName) + private void reorderNodeAssocs() { + // TODO + } + + public Long newNodeAssoc(Long sourceNodeId, Long targetNodeId, QName assocTypeQName, Long insertAfter) + { + // Touch to bring into current txn and ensure concurrency is maintained on the nodes + touchNodeImpl(sourceNodeId); + + // Resolve type QName Long assocTypeQNameId = qnameDAO.getOrCreateQName(assocTypeQName).getFirst(); + + // Do we allow ordering? + int assocIndex = 1; + // Only order if the association definition requires it + if (insertAfter != null) + { + if (insertAfter.longValue() == 0L) + { + assocIndex = 1; + } + else + { + // TODO: Figure out which index to use + // If the ID is invalid, we will get a concurrency condition + NodeAssocEntity afterAssoc = selectNodeAssocById(insertAfter); // We have checked for null already + assocIndex = afterAssoc.getAssocIndex() + 1; + } + // Update existing associations to make room for this one + // TODO: Reorder + } + else + { + // Nothing specified so use (max + 1) + int maxIndex = selectNodeAssocMaxIndex(sourceNodeId, assocTypeQNameId); + assocIndex = maxIndex + 1; + } + try { - // Touch to bring into current txn - touchNodeImpl(sourceNodeId); - - return insertNodeAssoc(sourceNodeId, targetNodeId, assocTypeQNameId); + return insertNodeAssoc(sourceNodeId, targetNodeId, assocTypeQNameId, assocIndex); } catch (Throwable e) { // Probably due to the association already existing. We throw a well-known - // exception and let retrying take itparameterObjects course + // exception and let retrying take its course throw new AssociationExistsException(sourceNodeId, targetNodeId, assocTypeQName, e); } } @@ -2359,9 +2392,21 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO return deleteNodeAssocsToAndFrom(nodeId, assocTypeQNameIds); } - public Collection> getSourceNodeAssocs(Long targetNodeId) + @Override + public Collection> getSourceNodeAssocs(Long targetNodeId, QName typeQName) { - List nodeAssocEntities = selectNodeAssocsByTarget(targetNodeId); + Long typeQNameId = null; + if (typeQName != null) + { + Pair typeQNamePair = qnameDAO.getQName(typeQName); + if (typeQNamePair == null) + { + // No such QName + return Collections.emptyList(); + } + typeQNameId = typeQNamePair.getFirst(); + } + List nodeAssocEntities = selectNodeAssocsByTarget(targetNodeId, typeQNameId); List> results = new ArrayList>(nodeAssocEntities.size()); for (NodeAssocEntity nodeAssocEntity : nodeAssocEntities) { @@ -2372,9 +2417,21 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO return results; } - public Collection> getTargetNodeAssocs(Long sourceNodeId) + @Override + public Collection> getTargetNodeAssocs(Long sourceNodeId, QName typeQName) { - List nodeAssocEntities = selectNodeAssocsBySource(sourceNodeId); + Long typeQNameId = null; + if (typeQName != null) + { + Pair typeQNamePair = qnameDAO.getQName(typeQName); + if (typeQNamePair == null) + { + // No such QName + return Collections.emptyList(); + } + typeQNameId = typeQNamePair.getFirst(); + } + List nodeAssocEntities = selectNodeAssocsBySource(sourceNodeId, typeQNameId); List> results = new ArrayList>(nodeAssocEntities.size()); for (NodeAssocEntity nodeAssocEntity : nodeAssocEntities) { @@ -3552,13 +3609,14 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO protected abstract void insertNodeAspect(Long nodeId, Long qnameId); protected abstract int deleteNodeAspects(Long nodeId, Set qnameIds); protected abstract void selectNodesWithAspect(Long qnameId, Long minNodeId, NodeRefQueryCallback resultsCallback); - protected abstract Long insertNodeAssoc(Long sourceNodeId, Long targetNodeId, Long assocTypeQNameId); + protected abstract Long insertNodeAssoc(Long sourceNodeId, Long targetNodeId, Long assocTypeQNameId, int assocIndex); protected abstract int deleteNodeAssoc(Long sourceNodeId, Long targetNodeId, Long assocTypeQNameId); protected abstract int deleteNodeAssocsToAndFrom(Long nodeId); protected abstract int deleteNodeAssocsToAndFrom(Long nodeId, Set assocTypeQNameIds); - protected abstract List selectNodeAssocsBySource(Long sourceNodeId); - protected abstract List selectNodeAssocsByTarget(Long targetNodeId); + protected abstract List selectNodeAssocsBySource(Long sourceNodeId, Long typeQNameId); + protected abstract List selectNodeAssocsByTarget(Long targetNodeId, Long typeQNameId); protected abstract NodeAssocEntity selectNodeAssocById(Long assocId); + protected abstract int selectNodeAssocMaxIndex(Long sourceNodeId, Long assocTypeQNameId); protected abstract Long insertChildAssoc(ChildAssocEntity assoc); protected abstract int deleteChildAssocById(Long assocId); protected abstract int updateChildAssocIndex( diff --git a/source/java/org/alfresco/repo/domain/node/NodeAssocEntity.java b/source/java/org/alfresco/repo/domain/node/NodeAssocEntity.java index 933082e5be..a78c449125 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeAssocEntity.java +++ b/source/java/org/alfresco/repo/domain/node/NodeAssocEntity.java @@ -37,6 +37,7 @@ public class NodeAssocEntity private NodeEntity sourceNode; private NodeEntity targetNode; private Long typeQNameId; + private int assocIndex; private List typeQNameIds; /** @@ -55,6 +56,7 @@ public class NodeAssocEntity .append(", sourceNode=").append(sourceNode) .append(", targetNode=").append(targetNode) .append(", typeQNameId=").append(typeQNameId) + .append(", assocIndex=").append(assocIndex) .append(", typeQNameIds=").append(typeQNameIds) .append("]"); return sb.toString(); @@ -124,6 +126,16 @@ public class NodeAssocEntity this.typeQNameId = typeQNameId; } + public int getAssocIndex() + { + return assocIndex; + } + + public void setAssocIndex(int assocIndex) + { + this.assocIndex = assocIndex; + } + public List getTypeQNameIds() { return typeQNameIds; diff --git a/source/java/org/alfresco/repo/domain/node/NodeDAO.java b/source/java/org/alfresco/repo/domain/node/NodeDAO.java index af30fabb8b..9205e0c636 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeDAO.java +++ b/source/java/org/alfresco/repo/domain/node/NodeDAO.java @@ -248,10 +248,15 @@ public interface NodeDAO extends NodeBulkLoader * Node Assocs */ - public Long newNodeAssoc( - Long sourceNodeId, - Long targetNodeId, - QName assocTypeQName); + /** + * Create a new association + * + * @param sourceNodeId the association source + * @param targetNodeId the association target + * @param assocTypeQName the type of the association (will be resolved to an ID) + * @param insertAfter ID of the association to preceed the new one (optional - add last) + */ + public Long newNodeAssoc(Long sourceNodeId, Long targetNodeId, QName assocTypeQName, Long insertAfter); /** * Remove a specific node association @@ -279,14 +284,18 @@ public interface NodeDAO extends NodeBulkLoader public int removeNodeAssocsToAndFrom(Long nodeId, Set assocTypeQNames); /** + * @param targetNodeId the target of the association + * @param typeQName the type of the association (optional) * @return Returns all the node associations where the node is the target */ - public Collection> getSourceNodeAssocs(Long targetNodeId); + public Collection> getSourceNodeAssocs(Long targetNodeId, QName typeQName); /** + * @param sourceNodeId the source of the association + * @param typeQName the type of the association (optional) * @return Returns all the node associations where the node is the source */ - public Collection> getTargetNodeAssocs(Long sourceNodeId); + public Collection> getTargetNodeAssocs(Long sourceNodeId, QName typeQName); /** * @return Returns a specific node association with the given ID 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 dbb0f9873b..720f48e4c3 100644 --- a/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java @@ -102,6 +102,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl private static final String SELECT_NODE_ASSOCS_BY_SOURCE = "alfresco.node.select_NodeAssocsBySource"; private static final String SELECT_NODE_ASSOCS_BY_TARGET = "alfresco.node.select_NodeAssocsByTarget"; private static final String SELECT_NODE_ASSOC_BY_ID = "alfresco.node.select_NodeAssocById"; + private static final String SELECT_NODE_ASSOCS_MAX_INDEX = "alfresco.node.select_NodeAssocsMaxId"; private static final String SELECT_NODE_PRIMARY_CHILD_ACLS = "alfresco.node.select_NodePrimaryChildAcls"; private static final String INSERT_CHILD_ASSOC = "alfresco.node.insert.insert_ChildAssoc"; private static final String DELETE_CHILD_ASSOC_BY_ID = "alfresco.node.delete_ChildAssocById"; @@ -643,7 +644,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl } @Override - protected Long insertNodeAssoc(Long sourceNodeId, Long targetNodeId, Long assocTypeQNameId) + protected Long insertNodeAssoc(Long sourceNodeId, Long targetNodeId, Long assocTypeQNameId, int assocIndex) { NodeAssocEntity assoc = new NodeAssocEntity(); assoc.setVersion(1L); @@ -656,6 +657,8 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl NodeEntity targetNode = new NodeEntity(); targetNode.setId(targetNodeId); assoc.setTargetNode(targetNode); + // Index + assoc.setAssocIndex(assocIndex); template.insert(INSERT_NODE_ASSOC, assoc); return assoc.getId(); @@ -713,26 +716,30 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl @SuppressWarnings("unchecked") @Override - protected List selectNodeAssocsBySource(Long sourceNodeId) + protected List selectNodeAssocsBySource(Long sourceNodeId, Long typeQNameId) { NodeAssocEntity assoc = new NodeAssocEntity(); // Source NodeEntity sourceNode = new NodeEntity(); sourceNode.setId(sourceNodeId); assoc.setSourceNode(sourceNode); + // Type + assoc.setTypeQNameId(typeQNameId); return (List) template.selectList(SELECT_NODE_ASSOCS_BY_SOURCE, assoc); } @SuppressWarnings("unchecked") @Override - protected List selectNodeAssocsByTarget(Long targetNodeId) + protected List selectNodeAssocsByTarget(Long targetNodeId, Long typeQNameId) { NodeAssocEntity assoc = new NodeAssocEntity(); // Target NodeEntity targetNode = new NodeEntity(); targetNode.setId(targetNodeId); assoc.setTargetNode(targetNode); + // Type + assoc.setTypeQNameId(typeQNameId); return (List) template.selectList(SELECT_NODE_ASSOCS_BY_TARGET, assoc); } @@ -746,6 +753,21 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl return (NodeAssocEntity) template.selectOne(SELECT_NODE_ASSOC_BY_ID, assoc); } + @Override + protected int selectNodeAssocMaxIndex(Long sourceNodeId, Long assocTypeQNameId) + { + NodeAssocEntity assoc = new NodeAssocEntity(); + // Source + NodeEntity sourceNode = new NodeEntity(); + sourceNode.setId(sourceNodeId); + assoc.setSourceNode(sourceNode); + // Assoc + assoc.setTypeQNameId(assocTypeQNameId); + + Integer maxIndex = (Integer) template.selectOne(SELECT_NODE_ASSOCS_MAX_INDEX, assoc); + return maxIndex == null ? 0 : maxIndex.intValue(); + } + @Override protected Long insertChildAssoc(ChildAssocEntity assoc) { diff --git a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java index 8e2277e4ea..4f7cfba520 100644 --- a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java @@ -58,6 +58,7 @@ import org.alfresco.service.cmr.dictionary.ClassDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.AssociationExistsException; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.InvalidNodeRefException; @@ -801,4 +802,11 @@ public abstract class AbstractNodeServiceImpl implements NodeService { return removeSecondaryChildAssociation(childAssocRef); } + + @Override + public final AssociationRef createAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) + throws InvalidNodeRefException, AssociationExistsException + { + return createAssociation(sourceRef, targetRef, assocTypeQName, null); + } } diff --git a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java index 90c82bc9d2..e56f783994 100644 --- a/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/BaseNodeServiceTest.java @@ -2406,24 +2406,34 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest } /** - * Creates a named association between two nodes - * - * @return Returns an array of [source real NodeRef][target reference NodeRef][assoc name String] + * Creates a named association between two new nodes */ private AssociationRef createAssociation() throws Exception { + return createAssociation(null, null); + } + + /** + * Creates an association between a given source and a new target + */ + private AssociationRef createAssociation(NodeRef sourceRef, Long insertAfter) throws Exception + { + // TODO: Use insertAfter Map properties = new HashMap(5); fillProperties(TYPE_QNAME_TEST_CONTENT, properties); fillProperties(ASPECT_QNAME_TEST_TITLED, properties); + if (sourceRef == null) + { + ChildAssociationRef childAssocRef = nodeService.createNode( + rootNodeRef, + ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName(null, "N1"), + TYPE_QNAME_TEST_CONTENT, + properties); + sourceRef = childAssocRef.getChildRef(); + } ChildAssociationRef childAssocRef = nodeService.createNode( - rootNodeRef, - ASSOC_TYPE_QNAME_TEST_CHILDREN, - QName.createQName(null, "N1"), - TYPE_QNAME_TEST_CONTENT, - properties); - NodeRef sourceRef = childAssocRef.getChildRef(); - childAssocRef = nodeService.createNode( rootNodeRef, ASSOC_TYPE_QNAME_TEST_CHILDREN, QName.createQName(null, "N2"), @@ -2511,6 +2521,60 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest } } + public void testTargetAssoc_NaturalOrdering() throws Exception + { + AssociationRef assocRef = createAssociation(); + NodeRef sourceRef = assocRef.getSourceRef(); + QName qname = assocRef.getTypeQName(); + + for (int i = 0; i < 99; i++) + { + assocRef = createAssociation(sourceRef, null); + } + + // Now get the associations and ensure that they are in order of ID + // because they should have been inserted in natural order + List assocs = nodeService.getTargetAssocs(sourceRef, ASSOC_TYPE_QNAME_TEST_NEXT); + Long lastId = 0L; + for (AssociationRef associationRef : assocs) + { + Long id = associationRef.getId(); + assertNotNull("Null association ID: " + associationRef, id); + assertTrue("Results should be in ID order", id > lastId); + lastId = id; + } + + setComplete(); + endTransaction(); + } + + public void DISABLED_testTargetAssoc_InverseOrdering() throws Exception + { + AssociationRef assocRef = createAssociation(); + NodeRef sourceRef = assocRef.getSourceRef(); + QName qname = assocRef.getTypeQName(); + + for (int i = 0; i < 99; i++) + { + assocRef = createAssociation(sourceRef, 0L); + } + + // Now get the associations and ensure that they are in order of ID + // because they should have been inserted in natural order + List assocs = nodeService.getTargetAssocs(sourceRef, ASSOC_TYPE_QNAME_TEST_NEXT); + Long lastId = 0L; + for (AssociationRef associationRef : assocs) + { + Long id = associationRef.getId(); + assertNotNull("Null association ID: " + associationRef, id); + assertTrue("Results should be in reverse ID order", id < lastId); + lastId = id; + } + + setComplete(); + endTransaction(); + } + public void testGetSourceAssocs() throws Exception { AssociationRef assocRef = createAssociation(); diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index 74fd3695be..fb3ffc40bb 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -1779,7 +1779,8 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl return assocRef; } - public AssociationRef createAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName) + public AssociationRef createAssociation( + NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName, Long insertAfter) throws InvalidNodeRefException, AssociationExistsException { Pair sourceNodePair = getNodePairNotNull(sourceRef); @@ -1787,8 +1788,18 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl Pair targetNodePair = getNodePairNotNull(targetRef); long targetNodeId = targetNodePair.getFirst(); + // Check if ordering is allowed + if (insertAfter != null) + { + AssociationDefinition assocDef = dictionaryService.getAssociation(assocTypeQName); + if (assocDef == null /*|| !assocDef.isOrdered()*/) + { + throw new IllegalArgumentException("Association type '" + assocTypeQName + "' is not ordered."); + } + } + // we are sure that the association doesn't exist - make it - Long assocId = nodeDAO.newNodeAssoc(sourceNodeId, targetNodeId, assocTypeQName); + Long assocId = nodeDAO.newNodeAssoc(sourceNodeId, targetNodeId, assocTypeQName, insertAfter); AssociationRef assocRef = new AssociationRef(assocId, sourceRef, assocTypeQName, targetRef); // Invoke policy behaviours @@ -1930,14 +1941,18 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl Pair sourceNodePair = getNodePairNotNull(sourceRef); long sourceNodeId = sourceNodePair.getFirst(); - // get all assocs to target - Collection> assocPairs = nodeDAO.getTargetNodeAssocs(sourceNodeId); + QName qnameFilter = null; + if (qnamePattern instanceof QName) + { + qnameFilter = (QName) qnamePattern; + } + Collection> assocPairs = nodeDAO.getTargetNodeAssocs(sourceNodeId, qnameFilter); List nodeAssocRefs = new ArrayList(assocPairs.size()); for (Pair assocPair : assocPairs) { AssociationRef assocRef = assocPair.getSecond(); - // check qname pattern - if (!qnamePattern.isMatch(assocRef.getTypeQName())) + // check qname pattern, if not already filtered + if (qnameFilter == null && !qnamePattern.isMatch(assocRef.getTypeQName())) { continue; // the assoc name doesn't match the pattern given } @@ -1951,15 +1966,19 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl { Pair targetNodePair = getNodePairNotNull(targetRef); long targetNodeId = targetNodePair.getFirst(); - - // get all assocs to target - Collection> assocPairs = nodeDAO.getSourceNodeAssocs(targetNodeId); + + QName qnameFilter = null; + if (qnamePattern instanceof QName) + { + qnameFilter = (QName) qnamePattern; + } + Collection> assocPairs = nodeDAO.getSourceNodeAssocs(targetNodeId, qnameFilter); List nodeAssocRefs = new ArrayList(assocPairs.size()); for (Pair assocPair : assocPairs) { AssociationRef assocRef = assocPair.getSecond(); - // check qname pattern - if (!qnamePattern.isMatch(assocRef.getTypeQName())) + // check qname pattern, if not already filtered + if (qnameFilter == null && !qnamePattern.isMatch(assocRef.getTypeQName())) { continue; // the assoc name doesn't match the pattern given } diff --git a/source/java/org/alfresco/repo/version/NodeServiceImpl.java b/source/java/org/alfresco/repo/version/NodeServiceImpl.java index 9fd7ef0b7a..a0b207feeb 100644 --- a/source/java/org/alfresco/repo/version/NodeServiceImpl.java +++ b/source/java/org/alfresco/repo/version/NodeServiceImpl.java @@ -597,6 +597,16 @@ public class NodeServiceImpl implements NodeService, VersionModel throw new UnsupportedOperationException(MSG_UNSUPPORTED); } + /** + * @throws UnsupportedOperationException always + */ + public AssociationRef createAssociation(NodeRef sourceRef, NodeRef targetRef, QName assocTypeQName, Long insertAfter) + throws InvalidNodeRefException, AssociationExistsException + { + // This operation is not supported for a version store + throw new UnsupportedOperationException(MSG_UNSUPPORTED); + } + /** * @throws UnsupportedOperationException always */