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()); + } + } + } + } +}