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
*/