diff --git a/config/alfresco/public-rest-context.xml b/config/alfresco/public-rest-context.xml
index 611057507d..f3d2ebd48f 100644
--- a/config/alfresco/public-rest-context.xml
+++ b/config/alfresco/public-rest-context.xml
@@ -775,6 +775,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/source/java/org/alfresco/rest/api/impl/NodesImpl.java b/source/java/org/alfresco/rest/api/impl/NodesImpl.java
index 61d144704c..b843f0cb80 100644
--- a/source/java/org/alfresco/rest/api/impl/NodesImpl.java
+++ b/source/java/org/alfresco/rest/api/impl/NodesImpl.java
@@ -804,6 +804,11 @@ public class NodesImpl implements Nodes
mapUserInfo = new HashMap<>(2);
}
+ if (includeParam == null)
+ {
+ includeParam = Collections.emptyList();
+ }
+
Node node;
Map properties = nodeService.getProperties(nodeRef);
@@ -895,7 +900,7 @@ public class NodesImpl implements Nodes
node.setAllowableOperations((allowableOperations.size() > 0 )? allowableOperations : null);
}
-
+
node.setNodeType(nodeTypeQName.toPrefixString(namespaceService));
node.setPath(pathInfo);
diff --git a/source/java/org/alfresco/rest/api/model/Assoc.java b/source/java/org/alfresco/rest/api/model/Assoc.java
new file mode 100644
index 0000000000..50613f950a
--- /dev/null
+++ b/source/java/org/alfresco/rest/api/model/Assoc.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.rest.api.model;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.rest.framework.resource.UniqueId;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.security.NoSuchPersonException;
+import org.alfresco.service.cmr.security.PersonService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.util.EqualsHelper;
+import org.apache.chemistry.opencmis.commons.data.PropertyData;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * @author janv
+ */
+public class Assoc
+{
+ private String prefixAssocTypeQName;
+
+ public Assoc()
+ {
+ }
+
+ public Assoc(String prefixAssocTypeQName)
+ {
+ this.prefixAssocTypeQName = prefixAssocTypeQName;
+ }
+
+ public String getAssocType()
+ {
+ return prefixAssocTypeQName;
+ }
+
+ public void setAssocType(String prefixAssocTypeQName)
+ {
+ this.prefixAssocTypeQName = prefixAssocTypeQName;
+ }
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/rest/api/model/AssocTarget.java b/source/java/org/alfresco/rest/api/model/AssocTarget.java
new file mode 100644
index 0000000000..1209d54d15
--- /dev/null
+++ b/source/java/org/alfresco/rest/api/model/AssocTarget.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.rest.api.model;
+
+/**
+ * @author janv
+ */
+public class AssocTarget extends Assoc
+{
+ private String targetId;
+
+ public AssocTarget()
+ {
+ }
+
+ public AssocTarget(String prefixAssocTypeQName)
+ {
+ super(prefixAssocTypeQName);
+ }
+
+ public AssocTarget(String targetId, String prefixAssocTypeQName)
+ {
+ super(prefixAssocTypeQName);
+
+ this.targetId = targetId;
+ }
+
+ public String getTargetId()
+ {
+ return targetId;
+ }
+
+ public void setTargetId(String targetId)
+ {
+ this.targetId = targetId;
+ }
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/rest/api/model/Node.java b/source/java/org/alfresco/rest/api/model/Node.java
index 1fc9d0a14c..f071e23517 100644
--- a/source/java/org/alfresco/rest/api/model/Node.java
+++ b/source/java/org/alfresco/rest/api/model/Node.java
@@ -425,6 +425,21 @@ public class Node implements Comparable
return this.contentInfo;
}
+ // when appropriate, can be used to show association (in the context of a listing), for example
+ // GET /nodes/parentId/children, /nodes/parentId/secondary-children, /nodes/childId/parents
+ // GET /nodes/sourceId/targets, /nodes/targetId/sources
+ protected Assoc association;
+
+ public Assoc getAssociation()
+ {
+ return association;
+ }
+
+ public void setAssociation(Assoc association)
+ {
+ this.association = association;
+ }
+
// TODO for backwards compat' - set explicitly when needed (ie. favourites) (note: we could choose to have separate old Node/NodeImpl etc)
diff --git a/source/java/org/alfresco/rest/api/nodes/NodeSourcesRelation.java b/source/java/org/alfresco/rest/api/nodes/NodeSourcesRelation.java
new file mode 100644
index 0000000000..78cad2c19a
--- /dev/null
+++ b/source/java/org/alfresco/rest/api/nodes/NodeSourcesRelation.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.rest.api.nodes;
+
+import org.activiti.engine.history.HistoricActivityInstance;
+import org.alfresco.repo.web.scripts.admin.NodeBrowserPost;
+import org.alfresco.rest.api.Nodes;
+import org.alfresco.rest.api.model.Assoc;
+import org.alfresco.rest.api.model.Comment;
+import org.alfresco.rest.api.model.Node;
+import org.alfresco.rest.api.model.UserInfo;
+import org.alfresco.rest.framework.WebApiDescription;
+import org.alfresco.rest.framework.WebApiParam;
+import org.alfresco.rest.framework.WebApiParameters;
+import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
+import org.alfresco.rest.framework.resource.RelationshipResource;
+import org.alfresco.rest.framework.resource.actions.interfaces.MultiPartRelationshipResourceAction;
+import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
+import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
+import org.alfresco.rest.framework.resource.parameters.Paging;
+import org.alfresco.rest.framework.resource.parameters.Parameters;
+import org.alfresco.rest.framework.webscripts.WithResponse;
+import org.alfresco.rest.workflow.api.model.Activity;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.repository.AssociationRef;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.search.ResultSetRow;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.namespace.RegexQNamePattern;
+import org.alfresco.util.ParameterCheck;
+import org.alfresco.util.PropertyCheck;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.extensions.webscripts.servlet.FormData;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Node Sources - list node (peer) associations from target to sources
+ *
+ * @author janv
+ */
+@RelationshipResource(name = "sources", entityResource = NodesEntityResource.class, title = "Node Sources")
+public class NodeSourcesRelation implements RelationshipResourceAction.Read, InitializingBean
+{
+ private ServiceRegistry sr;
+ private NodeService nodeService;
+ private NamespaceService namespaceService;
+ private Nodes nodes;
+
+ public void setNodes(Nodes nodes)
+ {
+ this.nodes = nodes;
+ }
+
+ public void setServiceRegistry(ServiceRegistry sr)
+ {
+ this.sr = sr;
+ }
+
+ @Override
+ public void afterPropertiesSet()
+ {
+ PropertyCheck.mandatory(this, "serviceRegistry", sr);
+ ParameterCheck.mandatory("nodes", this.nodes);
+
+ this.nodeService = sr.getNodeService();
+ this.namespaceService = sr.getNamespaceService();
+ }
+
+ /**
+ * List sources
+ *
+ * @param targetNodeId String id of target node
+ */
+ @Override
+ @WebApiDescription(title = "Return a paged list of sources nodes based on (peer) assocs")
+ public CollectionWithPagingInfo readAll(String targetNodeId, Parameters parameters)
+ {
+ NodeRef targetNodeRef = nodes.validateOrLookupNode(targetNodeId, null);
+
+ // TODO option to filter by assocType ... ?
+ List assocRefs = nodeService.getSourceAssocs(targetNodeRef, RegexQNamePattern.MATCH_ALL);
+
+ Map qnameMap = new HashMap<>(3);
+
+ Map mapUserInfo = new HashMap<>(10);
+
+ List includeParam = parameters.getInclude();
+
+ List collection = new ArrayList(assocRefs.size());
+ for (AssociationRef assocRef : assocRefs)
+ {
+ // minimal info by default (unless "include"d otherwise)
+ Node node = nodes.getFolderOrDocument(assocRef.getSourceRef(), null, null, includeParam, mapUserInfo);
+
+ QName assocTypeQName = assocRef.getTypeQName();
+ String assocType = qnameMap.get(assocTypeQName);
+ if (assocType == null)
+ {
+ assocType = assocTypeQName.toPrefixString(namespaceService);
+ qnameMap.put(assocTypeQName, assocType);
+ }
+ node.setAssociation(new Assoc(assocType));
+
+ collection.add(node);
+ }
+
+ Paging paging = parameters.getPaging();
+ return CollectionWithPagingInfo.asPaged(paging, collection, false, collection.size());
+ }
+}
diff --git a/source/java/org/alfresco/rest/api/nodes/NodeTargetsRelation.java b/source/java/org/alfresco/rest/api/nodes/NodeTargetsRelation.java
new file mode 100644
index 0000000000..38f1704984
--- /dev/null
+++ b/source/java/org/alfresco/rest/api/nodes/NodeTargetsRelation.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2005-2016 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+package org.alfresco.rest.api.nodes;
+
+import org.activiti.engine.history.HistoricActivityInstance;
+import org.alfresco.repo.web.scripts.admin.NodeBrowserPost;
+import org.alfresco.rest.api.Nodes;
+import org.alfresco.rest.api.model.Assoc;
+import org.alfresco.rest.api.model.AssocTarget;
+import org.alfresco.rest.api.model.Comment;
+import org.alfresco.rest.api.model.Node;
+import org.alfresco.rest.api.model.UserInfo;
+import org.alfresco.rest.framework.WebApiDescription;
+import org.alfresco.rest.framework.WebApiParam;
+import org.alfresco.rest.framework.WebApiParameters;
+import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException;
+import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
+import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
+import org.alfresco.rest.framework.resource.RelationshipResource;
+import org.alfresco.rest.framework.resource.actions.interfaces.MultiPartRelationshipResourceAction;
+import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
+import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
+import org.alfresco.rest.framework.resource.parameters.Paging;
+import org.alfresco.rest.framework.resource.parameters.Parameters;
+import org.alfresco.rest.framework.webscripts.WithResponse;
+import org.alfresco.rest.workflow.api.model.Activity;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.repository.AssociationExistsException;
+import org.alfresco.service.cmr.repository.AssociationRef;
+import org.alfresco.service.cmr.repository.InvalidNodeRefException;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.search.ResultSetRow;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.namespace.RegexQNamePattern;
+import org.alfresco.util.ParameterCheck;
+import org.alfresco.util.PropertyCheck;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.dao.ConcurrencyFailureException;
+import org.springframework.extensions.webscripts.servlet.FormData;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import static org.alfresco.repo.publishing.PublishingModel.ASSOC_LAST_PUBLISHING_EVENT;
+import static org.alfresco.util.collections.CollectionUtils.isEmpty;
+
+/**
+ * Node Targets
+ *
+ * - list node (peer) associations - from source to target
+ *
+ * @author janv
+ */
+@RelationshipResource(name = "targets", entityResource = NodesEntityResource.class, title = "Node Targets")
+public class NodeTargetsRelation implements
+ RelationshipResourceAction.Read,
+ RelationshipResourceAction.Create,
+ RelationshipResourceAction.Delete, InitializingBean
+{
+ private ServiceRegistry sr;
+ private NodeService nodeService;
+ private NamespaceService namespaceService;
+ private Nodes nodes;
+
+ public void setNodes(Nodes nodes)
+ {
+ this.nodes = nodes;
+ }
+
+ public void setServiceRegistry(ServiceRegistry sr)
+ {
+ this.sr = sr;
+ }
+
+ @Override
+ public void afterPropertiesSet()
+ {
+ PropertyCheck.mandatory(this, "serviceRegistry", sr);
+ ParameterCheck.mandatory("nodes", this.nodes);
+
+ this.nodeService = sr.getNodeService();
+ this.namespaceService = sr.getNamespaceService();
+ }
+
+ /**
+ * List targets
+ *
+ * @param sourceNodeId String id of source node
+ */
+ @Override
+ @WebApiDescription(title = "Return a paged list of target nodes based on (peer) assocs")
+ public CollectionWithPagingInfo readAll(String sourceNodeId, Parameters parameters)
+ {
+ NodeRef sourceNodeRef = nodes.validateOrLookupNode(sourceNodeId, null);
+
+ // TODO option to filter by assocType ... ?
+ List assocRefs = nodeService.getTargetAssocs(sourceNodeRef, RegexQNamePattern.MATCH_ALL);
+
+ Map qnameMap = new HashMap<>(3);
+
+ Map mapUserInfo = new HashMap<>(10);
+
+ List includeParam = parameters.getInclude();
+
+ List collection = new ArrayList(assocRefs.size());
+ for (AssociationRef assocRef : assocRefs)
+ {
+ // minimal info by default (unless "include"d otherwise)
+ Node node = nodes.getFolderOrDocument(assocRef.getTargetRef(), null, null, includeParam, mapUserInfo);
+
+ QName assocTypeQName = assocRef.getTypeQName();
+ String assocType = qnameMap.get(assocTypeQName);
+ if (assocType == null)
+ {
+ assocType = assocTypeQName.toPrefixString(namespaceService);
+ qnameMap.put(assocTypeQName, assocType);
+ }
+ node.setAssociation(new Assoc(assocType));
+
+ collection.add(node);
+ }
+
+ Paging paging = parameters.getPaging();
+ return CollectionWithPagingInfo.asPaged(paging, collection, false, collection.size());
+ }
+
+ @Override
+ @WebApiDescription(title="Add node assoc")
+ public List create(String sourceNodeId, List entity, Parameters parameters)
+ {
+ List result = new ArrayList<>(entity.size());
+
+ NodeRef srcNodeRef = nodes.validateNode(sourceNodeId);
+
+ for (AssocTarget assoc : entity)
+ {
+ String assocTypeStr = assoc.getAssocType();
+ if ((assocTypeStr == null) || assocTypeStr.isEmpty())
+ {
+ throw new InvalidArgumentException("Missing assocType");
+ }
+
+ QName assocTypeQName = QName.createQName(assocTypeStr, namespaceService);
+
+ try
+ {
+ // TODO consider x-store refs ?
+ NodeRef tgtNodeRef = nodes.validateNode(assoc.getTargetId());
+ nodeService.createAssociation(srcNodeRef, tgtNodeRef, assocTypeQName);
+ }
+ catch (AssociationExistsException aee)
+ {
+ throw new ConstraintViolatedException(aee.getMessage());
+ }
+
+ result.add(assoc);
+ }
+ return result;
+ }
+
+ @Override
+ @WebApiDescription(title = "Remove node assoc(s)")
+ public void delete(String sourceNodeId, String targetNodeId, Parameters parameters)
+ {
+ // TODO consider x-store refs ?
+ NodeRef srcNodeRef = nodes.validateNode(sourceNodeId);
+ NodeRef tgtNodeRef = nodes.validateNode(targetNodeId);
+
+ String assocTypeStr = parameters.getParameter("assocType");
+ if ((assocTypeStr != null) && (! assocTypeStr.isEmpty()))
+ {
+ QName assocTypeQName = QName.createQName(assocTypeStr, namespaceService);
+ nodeService.removeAssociation(srcNodeRef, tgtNodeRef, assocTypeQName);
+ }
+ else
+ {
+ List assocRefs = nodeService.getTargetAssocs(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, sourceNodeId), RegexQNamePattern.MATCH_ALL);
+ for (AssociationRef assocRef : assocRefs)
+ {
+ if (assocRef.getTargetRef().equals(tgtNodeRef))
+ {
+ nodeService.removeAssociation(srcNodeRef, tgtNodeRef, assocRef.getTypeQName());
+ }
+ }
+ }
+ }
+}