diff --git a/config/alfresco/public-rest-context.xml b/config/alfresco/public-rest-context.xml index 789323b30c..f4985b72d9 100644 --- a/config/alfresco/public-rest-context.xml +++ b/config/alfresco/public-rest-context.xml @@ -430,13 +430,8 @@ - - - - - - - + + diff --git a/source/java/org/alfresco/rest/api/Nodes.java b/source/java/org/alfresco/rest/api/Nodes.java index 42853ca643..075282251f 100644 --- a/source/java/org/alfresco/rest/api/Nodes.java +++ b/source/java/org/alfresco/rest/api/Nodes.java @@ -18,11 +18,14 @@ */ package org.alfresco.rest.api; +import java.io.InputStream; import java.util.Set; import org.alfresco.rest.api.model.Document; import org.alfresco.rest.api.model.Folder; import org.alfresco.rest.api.model.Node; +import org.alfresco.rest.framework.resource.content.BasicContentInfo; +import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.service.cmr.repository.NodeRef; @@ -89,13 +92,14 @@ public interface Nodes void deleteNode(String nodeId); /** + * Create node(s) - folder or (empty) file * * @param parentFolderNodeId - * @param folderInfo + * @param nodeInfo * @param parameters * @return */ - Folder createFolder(String parentFolderNodeId, Folder folderInfo, Parameters parameters); + Node createNode(String parentFolderNodeId, Node nodeInfo, Parameters parameters); /** * @@ -105,4 +109,10 @@ public interface Nodes * @return */ Node updateNode(String nodeId, Node entity, Parameters parameters); + + // TODO update REST fwk - to optionally support "attachment" (Content-Disposition) header + BinaryResource getContent(String fileNodeId, Parameters parameters); + + // TODO update REST fwk - to optionally support return of json + void updateContent(String fileNodeId, BasicContentInfo contentInfo, InputStream stream, Parameters parameters); } diff --git a/source/java/org/alfresco/rest/api/impl/NodesImpl.java b/source/java/org/alfresco/rest/api/impl/NodesImpl.java index 0b1d74ea8d..491a86d924 100644 --- a/source/java/org/alfresco/rest/api/impl/NodesImpl.java +++ b/source/java/org/alfresco/rest/api/impl/NodesImpl.java @@ -16,60 +16,59 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ -/* - * Copyright (C) 2005-2012 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.impl; +import org.alfresco.model.ContentModel; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.model.Repository; +import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; +import org.alfresco.rest.antlr.WhereClauseParser; +import org.alfresco.rest.api.Nodes; +import org.alfresco.rest.api.model.Document; +import org.alfresco.rest.api.model.Folder; +import org.alfresco.rest.api.model.Node; +import org.alfresco.rest.api.model.PathInfo; +import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; +import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; +import org.alfresco.rest.framework.resource.content.BasicContentInfo; +import org.alfresco.rest.framework.resource.content.BinaryResource; +import org.alfresco.rest.framework.resource.content.NodeBinaryResource; +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.resource.parameters.SortColumn; +import org.alfresco.rest.framework.resource.parameters.where.Query; +import org.alfresco.rest.framework.resource.parameters.where.QueryHelper; +import org.alfresco.rest.workflow.api.impl.MapBasedQueryWalker; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.repository.ContentWriter; +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.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; + +import java.io.InputStream; import java.io.Serializable; import java.util.AbstractList; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import org.alfresco.model.ContentModel; -import org.alfresco.opencmis.CMISConnector; -import org.alfresco.query.PagingRequest; -import org.alfresco.query.PagingResults; -import org.alfresco.repo.model.Repository; -import org.alfresco.rest.api.Nodes; -import org.alfresco.rest.api.model.Document; -import org.alfresco.rest.api.model.Folder; -import org.alfresco.rest.api.model.Node; -import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; -import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; -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.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.Path.Element; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; - /** * Centralises access to file/folder/node services and maps between representations. * @@ -88,51 +87,86 @@ public class NodesImpl implements Nodes private final static String PATH_ROOT = "-root-"; private final static String PATH_MY = "-my-"; + private final static String PATH_SHARED = "-shared-"; private NodeService nodeService; private DictionaryService dictionaryService; - private CMISConnector cmisConnector; private FileFolderService fileFolderService; - private Repository repositoryHelper; private NamespaceService namespaceService; private PermissionService permissionService; + private Repository repositoryHelper; + private ServiceRegistry sr; - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } + public void setServiceRegistry(ServiceRegistry sr) { + this.sr = sr; - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setCmisConnector(CMISConnector cmisConnector) - { - this.cmisConnector = cmisConnector; - } - - public void setFileFolderService(FileFolderService fileFolderService) - { - this.fileFolderService = fileFolderService; - } - - public void setRepositoryHelper(Repository repositoryHelper) - { - this.repositoryHelper = repositoryHelper; - } - - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - public void setPermissionService(PermissionService permissionService) - { - this.permissionService = permissionService; - } + this.namespaceService = sr.getNamespaceService(); + this.fileFolderService = sr.getFileFolderService(); + this.nodeService = sr.getNodeService(); + this.permissionService = sr.getPermissionService(); + this.dictionaryService = sr.getDictionaryService(); + } - /* + public void setRepositoryHelper(Repository repositoryHelper) + { + this.repositoryHelper = repositoryHelper; + } + + private static final List EXCLUDED_ASPECTS = Arrays.asList( + ContentModel.ASPECT_REFERENCEABLE, + ContentModel.ASPECT_LOCALIZED); + + private static final List EXCLUDED_PROPS = Arrays.asList( + // top-level basic info + ContentModel.PROP_NAME, + ContentModel.PROP_MODIFIER, + ContentModel.PROP_MODIFIED, + ContentModel.PROP_CREATOR, + ContentModel.PROP_CREATED, + ContentModel.PROP_CONTENT, + // sys:localized + ContentModel.PROP_LOCALE, + // sys:referenceable + ContentModel.PROP_NODE_UUID, + ContentModel.PROP_STORE_IDENTIFIER, + ContentModel.PROP_STORE_PROTOCOL, + ContentModel.PROP_NODE_DBID, + // other - TBC + ContentModel.PROP_INITIAL_VERSION, + ContentModel.PROP_AUTO_VERSION_PROPS, + ContentModel.PROP_AUTO_VERSION); + + private final static String PARAM_ISFOLDER = "isFolder"; + private final static String PARAM_NAME = "name"; + private final static String PARAM_CREATEDAT = "createdAt"; + private final static String PARAM_MODIFIEDAT = "modifiedAt"; + private final static String PARAM_CREATEBYUSER = "createdByUser"; + private final static String PARAM_MODIFIEDBYUSER = "modifiedByUser"; + private final static String PARAM_MIMETYPE = "mimeType"; + private final static String PARAM_SIZEINBYTES = "sizeInBytes"; + private final static String PARAM_NODETYPE = "nodeType"; + + private final static Map MAP_PARAM_QNAME; + static { + Map aMap = new HashMap<>(9); + + aMap.put(PARAM_ISFOLDER, GetChildrenCannedQuery.SORT_QNAME_NODE_IS_FOLDER); + aMap.put(PARAM_NAME, ContentModel.PROP_NAME); + aMap.put(PARAM_CREATEDAT, ContentModel.PROP_CREATED); + aMap.put(PARAM_MODIFIEDAT, ContentModel.PROP_MODIFIED); + aMap.put(PARAM_CREATEBYUSER, ContentModel.PROP_CREATOR); + aMap.put(PARAM_MODIFIEDBYUSER, ContentModel.PROP_MODIFIER); + aMap.put(PARAM_MIMETYPE, GetChildrenCannedQuery.SORT_QNAME_CONTENT_MIMETYPE); + aMap.put(PARAM_SIZEINBYTES, GetChildrenCannedQuery.SORT_QNAME_CONTENT_SIZE); + aMap.put(PARAM_NODETYPE, GetChildrenCannedQuery.SORT_QNAME_NODE_TYPE); + + MAP_PARAM_QNAME = Collections.unmodifiableMap(aMap); + } + + private final static Set LIST_FOLDER_CHILDREN_EQUALS_QUERY_PROPERTIES = + new HashSet<>(Arrays.asList(new String[] {PARAM_ISFOLDER})); + + /* * * Note: assumes workspace://SpacesStore */ @@ -163,7 +197,7 @@ public class NodesImpl implements Nodes public NodeRef validateNode(NodeRef nodeRef) { - if(!nodeService.exists(nodeRef)) + if (! nodeService.exists(nodeRef)) { throw new EntityNotFoundException(nodeRef.getId()); } @@ -172,27 +206,30 @@ public class NodesImpl implements Nodes } public boolean nodeMatches(NodeRef nodeRef, Set expectedTypes, Set excludedTypes) - { - if(!nodeService.exists(nodeRef)) - { - throw new EntityNotFoundException(nodeRef.getId()); - } + { + if (! nodeService.exists(nodeRef)) + { + throw new EntityNotFoundException(nodeRef.getId()); + } - QName type = nodeService.getType(nodeRef); + return typeMatches(nodeService.getType(nodeRef), expectedTypes, excludedTypes); + } - Set allExpectedTypes = new HashSet(); - if(expectedTypes != null) + protected boolean typeMatches(QName type, Set expectedTypes, Set excludedTypes) + { + Set allExpectedTypes = new HashSet<>(); + if (expectedTypes != null) { - for(QName expectedType : expectedTypes) + for (QName expectedType : expectedTypes) { allExpectedTypes.addAll(dictionaryService.getSubTypes(expectedType, true)); } } - Set allExcludedTypes = new HashSet(); - if(excludedTypes != null) + Set allExcludedTypes = new HashSet<>(); + if (excludedTypes != null) { - for(QName excludedType : excludedTypes) + for (QName excludedType : excludedTypes) { allExcludedTypes.addAll(dictionaryService.getSubTypes(excludedType, true)); } @@ -202,17 +239,23 @@ public class NodesImpl implements Nodes boolean excluded = allExcludedTypes.contains(type); return(inExpected && !excluded); } - + + /** + * @deprecated review usage (backward compat') + */ public Node getNode(String nodeId) { NodeRef nodeRef = validateNode(nodeId); - return new Node(nodeRef, nodeService.getProperties(nodeRef), namespaceService); + return new Node(nodeRef, null, nodeService.getProperties(nodeRef), sr); } - + + /** + * @deprecated review usage (backward compat') + */ public Node getNode(NodeRef nodeRef) { - return new Node(nodeRef, nodeService.getProperties(nodeRef), namespaceService); + return new Node(nodeRef, null, nodeService.getProperties(nodeRef), sr); } private Type getType(NodeRef nodeRef) @@ -227,65 +270,19 @@ public class NodesImpl implements Nodes return isContainer ? Type.FOLDER : Type.DOCUMENT; } - /* - // TODO filter CMIS properties - // TODO review & optimise - do we really need to go via CMIS properties !? - private Properties getCMISProperties(NodeRef nodeRef) - { - CMISNodeInfoImpl nodeInfo = cmisConnector.createNodeInfo(nodeRef); - final Properties properties = cmisConnector.getNodeProperties(nodeInfo, null); - - // fake the title property, which CMIS doesn't give us - String title = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); - final PropertyStringImpl titleProp = new PropertyStringImpl(ContentModel.PROP_TITLE.toString(), title); - Properties wrapProperties = new Properties() - { - @Override - public List getExtensions() - { - return properties.getExtensions(); - } - - @Override - public void setExtensions(List extensions) - { - properties.setExtensions(extensions); - } - - @Override - public Map> getProperties() - { - Map> updatedProperties = new HashMap>(properties.getProperties()); - updatedProperties.put(titleProp.getId(), titleProp); - return updatedProperties; - } - - @Override - public List> getPropertyList() - { - List> propertyList = new ArrayList>(properties.getPropertyList()); - propertyList.add(titleProp); - return propertyList; - } - }; - - return wrapProperties; - } - */ - /** * Returns the public api representation of a document. - * - * Note: properties are modelled after the OpenCMIS node properties + * + * @deprecated review usage (backward compat') */ public Document getDocument(NodeRef nodeRef) { Type type = getType(nodeRef); if (type.equals(Type.DOCUMENT)) { - //Properties properties = getCMISProperties(nodeRef); Map properties = nodeService.getProperties(nodeRef); - Document doc = new Document(nodeRef, properties, namespaceService); + + Document doc = new Document(nodeRef, getParentNodeRef(nodeRef), properties, sr); return doc; } else @@ -296,17 +293,17 @@ public class NodesImpl implements Nodes /** * Returns the public api representation of a folder. - * - * Note: properties are modelled after the OpenCMIS node properties + * + * @deprecated review usage (backward compat') */ public Folder getFolder(NodeRef nodeRef) { Type type = getType(nodeRef); if (type.equals(Type.FOLDER)) { - //Properties properties = getCMISProperties(nodeRef); Map properties = nodeService.getProperties(nodeRef); - Folder folder = new Folder(nodeRef, properties, namespaceService); + + Folder folder = new Folder(nodeRef, getParentNodeRef(nodeRef), properties, sr); return folder; } else @@ -314,13 +311,28 @@ public class NodesImpl implements Nodes throw new InvalidArgumentException("Node is not a folder"); } } + + private NodeRef getParentNodeRef(final NodeRef nodeRef) { + + if (repositoryHelper.getCompanyHome().equals(nodeRef)) + { + return null; // note: does not make sense to return parent above C/H + } + + return nodeService.getPrimaryParent(nodeRef).getParentRef(); + } private NodeRef validateOrLookupNode(String nodeId, String path) { - final NodeRef parentNodeRef; + NodeRef parentNodeRef; + if (nodeId.equals(PATH_ROOT)) { parentNodeRef = repositoryHelper.getCompanyHome(); } + else if (nodeId.equals(PATH_SHARED)) + { + parentNodeRef = repositoryHelper.getSharedHome(); + } else if (nodeId.equals(PATH_MY)) { NodeRef person = repositoryHelper.getPerson(); @@ -334,103 +346,272 @@ public class NodesImpl implements Nodes { parentNodeRef = validateNode(nodeId); } + + if (path != null) { + // resolve path relative to current nodeId + parentNodeRef = resolveNodeByPath(parentNodeRef, path, true); + } + return parentNodeRef; } + + protected NodeRef resolveNodeByPath(final NodeRef parentNodeRef, String path, boolean checkForCompanyHome) + { + final List pathElements = new ArrayList<>(0); + + if ((path != null) && (! path.isEmpty())) { + + if (path.startsWith("/")) { + path = path.substring(1); + } + + if (! path.isEmpty()) { + pathElements.addAll(Arrays.asList(path.split("/"))); + + if (checkForCompanyHome) + { + /* + if (nodeService.getRootNode(parentNodeRef.getStoreRef()).equals(parentNodeRef)) { + // special case + NodeRef chNodeRef = repositoryHelper.getCompanyHome(); + String chName = (String)nodeService.getProperty(chNodeRef, ContentModel.PROP_NAME); + if (chName.equals(pathElements.get(0))) { + pathElements = pathElements.subList(1, pathElements.size()); + parentNodeRef = chNodeRef; + } + } + */ + } + } + } + + FileInfo fileInfo = null; + try { + if (pathElements.size() != 0) { + fileInfo = fileFolderService.resolveNamePath(parentNodeRef, pathElements); + } + else + { + fileInfo = fileFolderService.getFileInfo(parentNodeRef); + if (fileInfo == null) + { + throw new FileNotFoundException(parentNodeRef); + } + } + } + catch (FileNotFoundException fnfe) { + // convert checked exception + throw new InvalidNodeRefException(fnfe.getMessage()+" ["+path+"]", parentNodeRef); + } + + return fileInfo.getNodeRef(); + } public Node getFolderOrDocument(String nodeId, Parameters parameters) { String path = parameters.getParameter("path"); - - boolean incPrimaryPath = false; - String str = parameters.getParameter("incPrimaryPath"); - if (str != null) { - incPrimaryPath = new Boolean(str); - } - NodeRef nodeRef = validateOrLookupNode(nodeId, path); + QName typeQName = nodeService.getType(nodeRef); - return getFolderOrDocument(nodeRef, typeQName, incPrimaryPath); + return getFolderOrDocument(nodeRef, getParentNodeRef(nodeRef), typeQName, false); } - private Node getFolderOrDocument(NodeRef nodeRef, QName typeQName,boolean incPrimaryPath) + private Node getFolderOrDocument(final NodeRef nodeRef, NodeRef parentNodeRef, QName typeQName, boolean minimalnfo) { - String primaryPath = null; - if (incPrimaryPath) + PathInfo pathInfo = null; + if (! minimalnfo) { - org.alfresco.service.cmr.repository.Path pp = nodeService.getPath(nodeRef); - - // Remove "app:company_home" (2nd element) - int ppSize = pp.size(); - if (ppSize > 1) { - if (ppSize == 2) { - pp = pp.subPath(0, 0); - } - else { - Element rootElement = pp.get(0); - pp = pp.subPath(2, ppSize-1).prepend(rootElement); - } - } - - primaryPath = pp.toDisplayPath(nodeService, permissionService); // note: slower (hence optional when getting node info) + pathInfo = lookupPathInfo(nodeRef); } - - Node node = null; + Type type = getType(typeQName); + Node node; Map properties = nodeService.getProperties(nodeRef); if (type.equals(Type.DOCUMENT)) { - //Properties properties = getCMISProperties(nodeRef); - node = new Document(nodeRef, properties, namespaceService); + node = new Document(nodeRef, parentNodeRef, properties, sr); } else if (type.equals(Type.FOLDER)) { // container/folder - //Properties properties = getCMISProperties(nodeRef); - node = new Folder(nodeRef, properties, namespaceService); + node = new Folder(nodeRef, parentNodeRef, properties, sr); } else { throw new InvalidArgumentException("Node is not a folder or file"); } - - node.setType(typeQName.toPrefixString(namespaceService)); - node.setPrimaryPath(primaryPath); // optional - can be null + + if (! minimalnfo) { + node.setProperties(mapProperties(properties)); + node.setAspectNames(mapAspects(nodeService.getAspects(nodeRef))); + } + + node.setNodeType(typeQName.toPrefixString(namespaceService)); + node.setPath(pathInfo); + return node; } - + + protected PathInfo lookupPathInfo(NodeRef nodeRefIn) + { + List elements = new ArrayList<>(5); + + NodeRef companyHomeNodeRef = repositoryHelper.getCompanyHome(); + boolean isComplete = true; + + NodeRef pNodeRef = nodeRefIn; + while (pNodeRef != null) + { + if (pNodeRef.equals(companyHomeNodeRef)) + { + pNodeRef = null; + } + else { + pNodeRef = nodeService.getPrimaryParent(pNodeRef).getParentRef(); + + if (pNodeRef == null) + { + // belts-and-braces - is it even possible to get here ? + isComplete = false; + } + else + { + if (permissionService.hasPermission(pNodeRef, PermissionService.READ) == AccessStatus.ALLOWED) + { + String name = (String) nodeService.getProperty(pNodeRef, ContentModel.PROP_NAME); + elements.add(0, new PathInfo().new ElementInfo(pNodeRef.getId(), name)); + } + else + { + isComplete = false; + pNodeRef = null; + } + } + } + } + + StringBuilder sb = new StringBuilder(); + for (PathInfo.ElementInfo e : elements) + { + sb.append("/").append(e.getName()); + } + + return new PathInfo(sb.toString(), isComplete, elements); + } + + protected Map mapProperties(Map nodeProps) + { + Map props = new HashMap<>(nodeProps.size()); + + for (Map.Entry entry : nodeProps.entrySet()) { + QName propQName = entry.getKey(); + if (!EXCLUDED_PROPS.contains(propQName)) + { + props.put(entry.getKey().toPrefixString(namespaceService), entry.getValue()); + } + } + + if (props.size() == 0) + { + props = null; // no props to return + } + + return props; + } + + protected List mapAspects(Set nodeAspects) + { + List aspectNames = new ArrayList<>(nodeAspects.size()); + + for (QName aspectName : nodeAspects) + { + if (! EXCLUDED_ASPECTS.contains(aspectName)) + { + aspectNames.add(aspectName.toPrefixString(namespaceService)); + } + } + + if (aspectNames.size() == 0) + { + aspectNames = null; // no aspects to return + } + + return aspectNames; + } + public CollectionWithPagingInfo getChildren(String parentFolderNodeId, Parameters parameters) { - // TODO consider using: where=(exists(target/file)) / where=(exists(target/file)) - // instead of: includeFiles=true / includeFolders=true - - boolean includeFolders = true; - String str = parameters.getParameter("includeFolders"); - if (str != null) { - includeFolders = new Boolean(str); + // TODO + // map - where (filter) properties - including isFolder + // map - orderBy (sort) properties - including isFolder + + // TODO refactor & fix ! + final boolean minimalnfo = (parameters.getSelectedProperties().size() == 0); + + boolean includeFolders = true; + boolean includeFiles = true; + + Query q = parameters.getQuery(); + + if (q != null) + { + // TODO confirm list of filter props - what about custom props (+ across types/aspects) ? + MapBasedQueryWalker propertyWalker = new MapBasedQueryWalker(LIST_FOLDER_CHILDREN_EQUALS_QUERY_PROPERTIES, null); + QueryHelper.walk(q, propertyWalker); + + Boolean b = propertyWalker.getProperty(PARAM_ISFOLDER, WhereClauseParser.EQUALS, Boolean.class); + if (b != null) + { + includeFiles = !b; + includeFolders = b; + } } - - boolean includeFiles = true; - str = parameters.getParameter("includeFiles"); - if (str != null) { - includeFiles = new Boolean(str); + + List sortCols = parameters.getSorting(); + List> sortProps = null; + if ((sortCols != null) && (sortCols.size() > 0)) + { + sortProps = new ArrayList<>(sortCols.size()); + for (SortColumn sortCol : sortCols) + { + QName propQname = MAP_PARAM_QNAME.get(sortCol.column); + if (propQname == null) + { + propQname = QName.resolveToQName(namespaceService, sortCol.column); + } + + if (propQname != null) + { + sortProps.add(new Pair<>(propQname, sortCol.asc)); + } + } } - - String path = parameters.getParameter("path"); - - Paging paging = parameters.getPaging(); + else + { + // default sort order + sortProps = new ArrayList<>(Arrays.asList( + new Pair<>(GetChildrenCannedQuery.SORT_QNAME_NODE_IS_FOLDER, Boolean.FALSE), + new Pair<>(ContentModel.PROP_NAME, true))); + } + + Paging paging = parameters.getPaging(); + + // TODO do we want to support path with list folder children ? + String path = null; + //String path = parameters.getParameter("path"); + final NodeRef parentNodeRef = validateOrLookupNode(parentFolderNodeId, path); - - final Set folders = new HashSet<>(Arrays.asList(ContentModel.TYPE_FOLDER)); - if (! nodeMatches(parentNodeRef, folders, null)) + + if (! nodeMatches(parentNodeRef, Collections.singleton(ContentModel.TYPE_FOLDER), null)) { throw new InvalidArgumentException("NodeId of folder is expected"); } PagingRequest pagingRequest = Util.getPagingRequest(paging); - final PagingResults pagingResults = fileFolderService.list(parentNodeRef, includeFiles, includeFolders, null, null, pagingRequest); + final PagingResults pagingResults = fileFolderService.list(parentNodeRef, includeFiles, includeFolders, null, sortProps, pagingRequest); final List page = pagingResults.getPage(); List nodes = new AbstractList() @@ -439,7 +620,9 @@ public class NodesImpl implements Nodes public Node get(int index) { FileInfo fInfo = page.get(index); - return getFolderOrDocument(fInfo.getNodeRef(), fInfo.getType(), false); + + // basic info by default (unless "select"ed otherwise) + return getFolderOrDocument(fInfo.getNodeRef(), parentNodeRef, fInfo.getType(), minimalnfo); } @Override @@ -458,48 +641,68 @@ public class NodesImpl implements Nodes fileFolderService.delete(nodeRef); } - public Folder createFolder(String parentFolderNodeId, Folder folderInfo, Parameters parameters) + // TODO should we able to specify content properties (eg. mimeType ... or use extension for now, or encoding) + public Node createNode(String parentFolderNodeId, Node nodeInfo, Parameters parameters) { - final NodeRef parentNodeRef = validateNode(parentFolderNodeId); + // check that requested parent node exists and it's type is a (sub-)type of folder + final NodeRef parentNodeRef = validateOrLookupNode(parentFolderNodeId, null); - final Set folders = new HashSet<>(Arrays.asList(ContentModel.TYPE_FOLDER)); - if (! nodeMatches(parentNodeRef, folders, null)) + if (! nodeMatches(parentNodeRef, Collections.singleton(ContentModel.TYPE_FOLDER), null)) { - throw new InvalidArgumentException("NodeId of folder is expected"); + throw new InvalidArgumentException("NodeId of folder is expected: "+parentNodeRef); } - String folderName = folderInfo.getName(); - String folderType = folderInfo.getType(); - if (folderType == null) { - folderType = "cm:folder"; - } - - QName folderTypeQName = QName.resolveToQName(namespaceService, folderType); - - Map props = new HashMap(10); - - props.put(ContentModel.PROP_NAME, folderName); - - String title = folderInfo.getTitle(); - if ((title != null) && (! title.isEmpty())) { - props.put(ContentModel.PROP_TITLE, title); + String nodeName = nodeInfo.getName(); + if ((nodeName == null) || nodeName.isEmpty()) + { + throw new InvalidArgumentException("Node name is expected: "+parentNodeRef); } - String description = folderInfo.getDescription(); - if ((description != null) && (! description.isEmpty())) { - props.put(ContentModel.PROP_DESCRIPTION, description); + String nodeType = nodeInfo.getNodeType(); + if ((nodeType == null) || nodeType.isEmpty()) + { + throw new InvalidArgumentException("Node type is expected: "+parentNodeRef+","+nodeName); } - // TODO other custom properties !! + // check that requested type is a (sub-) type of folder or content + QName nodeTypeQName = QName.resolveToQName(namespaceService, nodeType); - QName assocQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(folderName)); + Set contentAndFolders = new HashSet<>(Arrays.asList(ContentModel.TYPE_FOLDER, ContentModel.TYPE_CONTENT)); + if (! typeMatches(nodeTypeQName, contentAndFolders, null)) { + throw new InvalidArgumentException("Type of folder or content is expected: "+ nodeType); + } - NodeRef nodeRef = nodeService.createNode(parentNodeRef, ContentModel.ASSOC_CONTAINS, assocQName, folderTypeQName, props).getChildRef(); + boolean isContent = typeMatches(nodeTypeQName, Collections.singleton(ContentModel.TYPE_CONTENT), null); - return (Folder) getFolderOrDocument(nodeRef.getId(), parameters); + Map props = new HashMap<>(10); + + if (nodeInfo.getProperties() != null) + { + for (Map.Entry entry : (Set>)nodeInfo.getProperties().entrySet()) + { + QName propQName = QName.createQName((String)entry.getKey(), namespaceService); + props.put(propQName, (Serializable)entry.getValue()); + } + } + + props.put(ContentModel.PROP_NAME, nodeName); + + QName assocQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(nodeName)); + NodeRef nodeRef = nodeService.createNode(parentNodeRef, ContentModel.ASSOC_CONTAINS, assocQName, nodeTypeQName, props).getChildRef(); + + if (isContent) { + // add empty file + ContentWriter writer = sr.getContentService().getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + String mimeType = sr.getMimetypeService().guessMimetype(nodeName); + writer.setMimetype(mimeType); + writer.putContent(""); + } + + return getFolderOrDocument(nodeRef.getId(), parameters); } - public Node updateNode(String nodeId, Node nodeInfo, Parameters parameters) { + public Node updateNode(String nodeId, Node nodeInfo, Parameters parameters) + { final NodeRef nodeRef = validateNode(nodeId); @@ -511,26 +714,72 @@ public class NodesImpl implements Nodes Map props = new HashMap<>(10); + if (nodeInfo.getProperties() != null) + { + for (Map.Entry entry : (Set>)nodeInfo.getProperties().entrySet()) + { + QName propQName = QName.createQName((String)entry.getKey(), namespaceService); + props.put(propQName, (Serializable)entry.getValue()); + } + } + String name = nodeInfo.getName(); - if ((name != null) && (! name.isEmpty())) { + if ((name != null) && (! name.isEmpty())) + { // note: this is equivalent of a rename within target folder props.put(ContentModel.PROP_NAME, name); } - String title = nodeInfo.getTitle(); - if ((title != null) && (! title.isEmpty())) { - props.put(ContentModel.PROP_TITLE, title); - } - - String description = nodeInfo.getDescription(); - if ((description != null) && (! description.isEmpty())) { - props.put(ContentModel.PROP_DESCRIPTION, description); - } - - if (props.size() > 0) { + if (props.size() > 0) + { nodeService.addProperties(nodeRef, props); } return getFolderOrDocument(nodeRef.getId(), parameters); } + + @Override + public BinaryResource getContent(String fileNodeId, Parameters parameters) + { + final NodeRef nodeRef = validateNode(fileNodeId); + + if (! nodeMatches(nodeRef, Collections.singleton(ContentModel.TYPE_CONTENT), null)) + { + throw new InvalidArgumentException("NodeId of content is expected: "+nodeRef); + } + + // TODO attachment header - update (or extend ?) REST fwk + return new NodeBinaryResource(nodeRef, ContentModel.PROP_CONTENT); + } + + @Override + public void updateContent(String fileNodeId, BasicContentInfo contentInfo, InputStream stream, Parameters parameters) + { + final NodeRef nodeRef = validateNode(fileNodeId); + + if (! nodeMatches(nodeRef, Collections.singleton(ContentModel.TYPE_CONTENT), null)) + { + throw new InvalidArgumentException("NodeId of content is expected: "+nodeRef); + } + + ContentWriter writer = sr.getContentService().getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + + String mimeType = contentInfo.getMimeType(); + if (mimeType == null) + { + String fileName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + writer.guessMimetype(fileName); + } + else + { + writer.setMimetype(mimeType); + } + + writer.guessEncoding(); + + writer.putContent(stream); + + // TODO - hmm - we may wish to return json info !! + return; + } } diff --git a/source/java/org/alfresco/rest/api/model/ContentInfo.java b/source/java/org/alfresco/rest/api/model/ContentInfo.java new file mode 100644 index 0000000000..a7f2823452 --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/ContentInfo.java @@ -0,0 +1,50 @@ +package org.alfresco.rest.api.model; + +/** + * Representation of content info + * + * @author janv + * + */ +public class ContentInfo +{ + private String mimeType; + private String mimeTypeName; + private long sizeInBytes; + private String encoding; + + public ContentInfo() + { + } + + public ContentInfo( String mimeType, String mimeTypeName, long sizeInBytes, String encoding) + { + this.mimeType = mimeType; + this.mimeTypeName = mimeTypeName; + this.sizeInBytes = sizeInBytes; + this.encoding = encoding; + } + + public String getMimeType() { + return mimeType; + } + + public String getMimeTypeName() { + return mimeTypeName; + } + + public long getSizeInBytes() { + return sizeInBytes; + } + + public String getEncoding() { + return encoding; + } + + @Override + public String toString() + { + return "ContentInfo [mimeType=" + mimeType + ", mimeTypeName=" + mimeTypeName + + ", encoding=" + encoding + ", sizeInBytes=" + sizeInBytes + "]"; + } +} diff --git a/source/java/org/alfresco/rest/api/model/Document.java b/source/java/org/alfresco/rest/api/model/Document.java index 0d5210c1d6..cb679c26d8 100644 --- a/source/java/org/alfresco/rest/api/model/Document.java +++ b/source/java/org/alfresco/rest/api/model/Document.java @@ -1,15 +1,32 @@ +/* + * Copyright (C) 2005-2015 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.math.BigInteger; import java.util.Map; +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; -import org.apache.chemistry.opencmis.commons.PropertyIds; -import org.apache.chemistry.opencmis.commons.data.Properties; -import org.apache.chemistry.opencmis.commons.data.PropertyData; /** * Representation of a document node. @@ -20,31 +37,32 @@ import org.apache.chemistry.opencmis.commons.data.PropertyData; */ public class Document extends Node { + // TODO backward compat' - favourites etc private String mimeType; private BigInteger sizeInBytes; - private String versionLabel; + private String versionLabel; - public Document() - { - super(); - } + private ContentInfo contentInfo; - /* - public Document(NodeRef nodeRef, Properties properties) - { - super(nodeRef, properties); + public Document() { + super(); + } - Map> props = properties.getProperties(); - this.mimeType = (String)getValue(props, PropertyIds.CONTENT_STREAM_MIME_TYPE); - this.sizeInBytes = (BigInteger)getValue(props, PropertyIds.CONTENT_STREAM_LENGTH); - this.versionLabel = (String)getValue(props, PropertyIds.VERSION_LABEL); - } - */ + public Document(NodeRef nodeRef, NodeRef parentNodeRef, Map nodeProps, ServiceRegistry sr) + { + super(nodeRef, parentNodeRef, nodeProps, sr); - public Document(NodeRef nodeRef, Map nodeProps, NamespaceService namespaceService) - { - super(nodeRef, nodeProps, namespaceService); - } + Serializable val = nodeProps.get(ContentModel.PROP_CONTENT); + + if ((val != null) && (val instanceof ContentData)) { + ContentData cd = (ContentData)val; + String mimeType = cd.getMimetype(); + String mimeTypeName = sr.getMimetypeService().getDisplaysByMimetype().get(mimeType); + this.contentInfo = new ContentInfo(mimeType, mimeTypeName, cd.getSize(), cd.getEncoding()); + } + + //this.versionLabel = (String)nodeProps.get(ContentModel.PROP_VERSION_LABEL); + } public String getMimeType() { @@ -66,13 +84,16 @@ public class Document extends Node return false; } + public ContentInfo getContent() + { + return contentInfo; + } + @Override public String toString() { - return "Document [mimeType=" + mimeType + ", sizeInBytes=" - + sizeInBytes + ", versionLabel=" + versionLabel + ", nodeRef=" - + nodeRef + ", name=" + name + ", title=" + title - + ", description=" + description + ", createdAt=" + createdAt + return "Document [contentInfo=" + contentInfo.toString() + ", nodeRef=" + + nodeRef + ", name=" + name + ", createdAt=" + createdAt + ", modifiedAt=" + modifiedAt + ", createdBy=" + createdBy + ", modifiedBy=" + modifiedBy + "]"; } diff --git a/source/java/org/alfresco/rest/api/model/Folder.java b/source/java/org/alfresco/rest/api/model/Folder.java index 20f1395893..2881191af7 100644 --- a/source/java/org/alfresco/rest/api/model/Folder.java +++ b/source/java/org/alfresco/rest/api/model/Folder.java @@ -1,12 +1,29 @@ +/* + * Copyright (C) 2005-2015 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.Map; +import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; -import org.apache.chemistry.opencmis.commons.data.Properties; /** * Representation of a folder node. @@ -22,17 +39,10 @@ public class Folder extends Node super(); } - /* - public Folder(NodeRef nodeRef, Properties properties) - { - super(nodeRef, properties); - } - */ - - public Folder(NodeRef nodeRef, Map nodeProps, NamespaceService namespaceService) - { - super(nodeRef, nodeProps, namespaceService); - } + public Folder(NodeRef nodeRef, NodeRef parentNodeRef, Map nodeProps, ServiceRegistry sr) + { + super(nodeRef, parentNodeRef, nodeProps, sr); + } public Boolean getIsFolder() { diff --git a/source/java/org/alfresco/rest/api/model/Node.java b/source/java/org/alfresco/rest/api/model/Node.java index 6a53801899..09cb41e194 100644 --- a/source/java/org/alfresco/rest/api/model/Node.java +++ b/source/java/org/alfresco/rest/api/model/Node.java @@ -19,167 +19,139 @@ package org.alfresco.rest.api.model; import java.io.Serializable; -import java.util.Arrays; import java.util.Date; -import java.util.GregorianCalendar; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; -import com.fasterxml.jackson.annotation.JsonAnyGetter; 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.namespace.NamespaceService; +import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.namespace.QName; import org.alfresco.util.EqualsHelper; -import org.apache.chemistry.opencmis.commons.PropertyIds; -import org.apache.chemistry.opencmis.commons.data.Properties; import org.apache.chemistry.opencmis.commons.data.PropertyData; /** * Concrete class carrying general information for alf_node data - * + * * @author steveglover * @author Gethin James * @author janv */ public class Node implements Comparable { - protected NodeRef nodeRef; - protected String name; - protected String title; - protected NodeRef guid; // TODO review - do we need for favorites (backwards compat') ? - protected String description; - protected Date createdAt; - protected Date modifiedAt; + protected NodeRef nodeRef; + protected String name; + + // TODO needed for favourties - backwards compat' - we could also choose to split of NodeInfo / Node impl's etc + protected String title; + protected NodeRef guid; + protected String description; protected String createdBy; protected String modifiedBy; - - protected String primaryPath; + + protected Date createdAt; + protected Date modifiedAt; + protected UserInfo createdByUser; + protected UserInfo modifiedByUser; + + protected NodeRef parentNodeRef; + protected PathInfo pathInfo; protected String prefixTypeQName; + protected List aspectNames; + protected Map props; - private static final List EXCLUDED_PROPS = Arrays.asList( - ContentModel.PROP_NAME, - ContentModel.PROP_TITLE, - ContentModel.PROP_DESCRIPTION, - ContentModel.PROP_MODIFIER, - ContentModel.PROP_MODIFIED, - ContentModel.PROP_CREATOR, - ContentModel.PROP_CREATED, - ContentModel.PROP_CONTENT, - ContentModel.PROP_LOCALE, - ContentModel.PROP_NODE_UUID, - ContentModel.PROP_STORE_IDENTIFIER, - ContentModel.PROP_STORE_PROTOCOL, - ContentModel.PROP_NODE_DBID, - ContentModel.PROP_INITIAL_VERSION, - ContentModel.PROP_AUTO_VERSION_PROPS, - ContentModel.PROP_AUTO_VERSION); - // TODO fixme ! - public Node(NodeRef nodeRef, Map nodeProps, NamespaceService namespaceService) + // also need to optionally pass in user map - eg. when listing children (to avoid multiple lookups for same user) + public Node(NodeRef nodeRef, NodeRef parentNodeRef, Map nodeProps, ServiceRegistry sr) { - if(nodeRef == null) - { - throw new IllegalArgumentException(); - } + if(nodeRef == null) + { + throw new IllegalArgumentException(); + } - this.nodeRef = nodeRef; - mapProperties(nodeProps, namespaceService); + this.nodeRef = nodeRef; + this.parentNodeRef = parentNodeRef; + + mapBasicInfo(nodeProps, sr); } - - protected Object getValue(Map> props, String name) - { - PropertyData prop = props.get(name); - Object value = (prop != null ? prop.getFirstValue() : null); - return value; - } - /* - public Node(NodeRef nodeRef, Properties properties) - { - this.nodeRef = nodeRef; - - Map> props = properties.getProperties(); - - this.guid = nodeRef; - - this.name = (String)getValue(props, PropertyIds.NAME); - this.title = (String)getValue(props, ContentModel.PROP_TITLE.toString()); - this.description = (String)getValue(props, PropertyIds.DESCRIPTION); - - GregorianCalendar cal = (GregorianCalendar)getValue(props, PropertyIds.CREATION_DATE); - this.createdAt = cal.getTime(); - cal = (GregorianCalendar)getValue(props, PropertyIds.LAST_MODIFICATION_DATE); - this.modifiedAt = cal.getTime(); - this.createdBy = (String)getValue(props, PropertyIds.CREATED_BY); - this.modifiedBy = (String)getValue(props, PropertyIds.LAST_MODIFIED_BY); - } - */ + protected Object getValue(Map> props, String name) + { + PropertyData prop = props.get(name); + Object value = (prop != null ? prop.getFirstValue() : null); + return value; + } public Node() { } - protected void mapProperties(Map nodeProps, NamespaceService namespaceService) + protected void mapBasicInfo(Map nodeProps, ServiceRegistry sr) { + PersonService personService = sr.getPersonService(); + // TODO review backwards compat' for favorites & others (eg. set guid explicitly where still needed) //this.guid = nodeRef; + //this.title = (String)nodeProps.get(ContentModel.PROP_TITLE); + //this.description = (String)nodeProps.get(ContentModel.PROP_DESCRIPTION); + //this.createdBy = (String)nodeProps.get(ContentModel.PROP_CREATOR); + //this.modifiedBy = (String)nodeProps.get(ContentModel.PROP_MODIFIER); - this.name = (String)nodeProps.get(ContentModel.PROP_NAME); - this.title = (String)nodeProps.get(ContentModel.PROP_TITLE); - this.description = (String)nodeProps.get(ContentModel.PROP_DESCRIPTION); + this.name = (String)nodeProps.get(ContentModel.PROP_NAME); - this.createdAt = (Date)nodeProps.get(ContentModel.PROP_CREATED); - this.createdBy = (String)nodeProps.get(ContentModel.PROP_CREATOR); - this.modifiedAt = (Date)nodeProps.get(ContentModel.PROP_MODIFIED); - this.modifiedBy = (String)nodeProps.get(ContentModel.PROP_MODIFIER); + this.createdAt = (Date)nodeProps.get(ContentModel.PROP_CREATED); + this.createdByUser = lookupUserInfo((String)nodeProps.get(ContentModel.PROP_CREATOR), personService); - this.props = new HashMap<>(nodeProps.size()); + this.modifiedAt = (Date)nodeProps.get(ContentModel.PROP_MODIFIED); + this.modifiedByUser = lookupUserInfo((String)nodeProps.get(ContentModel.PROP_MODIFIER), personService); + } - for (Map.Entry entry : nodeProps.entrySet()) { - QName propQName = entry.getKey(); - if (! EXCLUDED_PROPS.contains(propQName)) { - props.put(entry.getKey().toPrefixString(namespaceService), entry.getValue()); - } + // TODO refactor & optimise to avoid multiple person lookups + private UserInfo lookupUserInfo(final String userName, final PersonService personService) { + + String sysUserName = AuthenticationUtil.getSystemUserName(); + if (userName.equals(sysUserName) || (AuthenticationUtil.isMtEnabled() && userName.startsWith(sysUserName+"@"))) + { + return new UserInfo(userName, userName, ""); + } + else + { + PersonService.PersonInfo pInfo = personService.getPerson(personService.getPerson(userName)); + return new UserInfo(userName, pInfo.getFirstName(), pInfo.getLastName()); } } - + public void setGuid(NodeRef guid) - { - this.guid = guid; - } + { + this.guid = guid; + } - public NodeRef getGuid() - { - return guid; - } + public NodeRef getGuid() { + return guid; + } - public String getTitle() - { - return title; - } + public String getTitle() + { + return title; + } - @UniqueId + @UniqueId public NodeRef getNodeRef() { - return nodeRef; - } + return nodeRef; + } - public void setNodeRef(NodeRef nodeRef) - { -// if(nodeRef == null) -// { -// throw new IllegalArgumentException(); -// } - this.nodeRef = nodeRef; - } - - public Date getCreatedAt() + public void setNodeRef(NodeRef nodeRef) + { + this.nodeRef = nodeRef; + } + + public Date getCreatedAt() { return this.createdAt; } @@ -189,20 +161,28 @@ public class Node implements Comparable this.createdAt = createdAt; } - public Date getModifiedAt() - { - return modifiedAt; - } + public Date getModifiedAt() + { + return modifiedAt; + } - public String getModifiedBy() - { - return modifiedBy; - } + public String getModifiedBy() + { + return modifiedBy; + } + + public UserInfo getModifiedByUser() { + return modifiedByUser; + } + + public UserInfo getCreatedByUser() { + return createdByUser; + } public String getDescription() - { - return description; - } + { + return description; + } public String getName() { @@ -223,59 +203,76 @@ public class Node implements Comparable { this.createdBy = createdBy; } - - public String getPrimaryPath() - { - return primaryPath; - } - - public void setPrimaryPath(String primaryPath) - { - this.primaryPath = primaryPath; - } - - public String getType() - { - return prefixTypeQName; - } - - public void setType(String prefixType) - { - this.prefixTypeQName = prefixType; + + public PathInfo getPath() + { + return pathInfo; } - public Map getProperties() { + public void setPath(PathInfo pathInfo) + { + this.pathInfo = pathInfo; + } + + public String getNodeType() + { + return prefixTypeQName; + } + + public void setNodeType(String prefixType) + { + this.prefixTypeQName = prefixType; + } + + public Map getProperties() { return this.props; } - - public boolean equals(Object other) - { - if(this == other) - { - return true; - } - if(!(other instanceof Node)) - { - return false; - } - - Node node = (Node)other; - return EqualsHelper.nullSafeEquals(getNodeRef(), node.getNodeRef()); - } + public void setProperties(Map props) { + this.props = props; + } - @Override - public int compareTo(Node node) - { - return getNodeRef().toString().compareTo(node.getNodeRef().toString()); - } + public List getAspectNames() { + return aspectNames; + } - @Override - public String toString() - { - return "Node [nodeRef=" + nodeRef + ", type=" + prefixTypeQName + ", name=" + name + ", title=" - + title + ", description=" + description + ", createdAt=" - + createdAt + ", modifiedAt=" + modifiedAt + ", createdBy=" + createdBy + ", modifiedBy=" - + modifiedBy + ", primaryPath =" + primaryPath +"]"; - } -} + public void setAspectNames(List aspectNames) { + this.aspectNames = aspectNames; + } + + public NodeRef getParentId() + { + return parentNodeRef; + } + + public boolean equals(Object other) + { + if(this == other) + { + return true; + } + + if(!(other instanceof Node)) + { + return false; + } + + Node node = (Node)other; + return EqualsHelper.nullSafeEquals(getNodeRef(), node.getNodeRef()); + } + + @Override + public int compareTo(Node node) + { + return getNodeRef().toString().compareTo(node.getNodeRef().toString()); + } + + @Override + public String toString() + { + return "Node [nodeRef=" + nodeRef + ", type=" + prefixTypeQName + ", name=" + name + ", title=" + + title + ", description=" + description + ", createdAt=" + + createdAt + ", modifiedAt=" + modifiedAt + ", createdByUser=" + createdByUser + ", modifiedBy=" + + modifiedByUser + ", pathInfo =" + pathInfo +"]"; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/rest/api/model/PathInfo.java b/source/java/org/alfresco/rest/api/model/PathInfo.java new file mode 100644 index 0000000000..8c0f96e06f --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/PathInfo.java @@ -0,0 +1,64 @@ +package org.alfresco.rest.api.model; + +import java.util.List; + +/** + * Representation of a path info + * + * @author janv + * + */ +public class PathInfo +{ + private String name; + private Boolean isComplete; + private List elements; + + public PathInfo() + { + } + + public PathInfo(String name, Boolean isComplete, List elements) + { + this.name = name; + this.isComplete = isComplete; + this.elements = elements; + } + + public String getName() { + return name; + } + + public Boolean getIsComplete() { + return isComplete; + } + + public List getElements() { + return elements; + } + + public class ElementInfo { + + private String id; + + private String name; + + public ElementInfo() + { + } + + public ElementInfo(String id, String name) + { + this.id = id; + this.name = name; + } + + public String getName() { + return name; + } + + public String getId() { + return id; + } + } +} diff --git a/source/java/org/alfresco/rest/api/model/UserInfo.java b/source/java/org/alfresco/rest/api/model/UserInfo.java new file mode 100644 index 0000000000..fe78dc204b --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/UserInfo.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005-2015 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; + +/** + * Representation of a user info + * + * @author janv + * + */ +public class UserInfo +{ + private String userName; + private String displayName; + + public UserInfo() + { + } + + public UserInfo(String userName, String firstName, String lastName) + { + this.userName = userName; + this.displayName = ((firstName != null ? firstName + " " : "") + (lastName != null ? lastName : "")).replaceAll("^\\s+|\\s+$", ""); + } + + public String getDisplayName() { + return displayName; + } + + public String getUserName() { + return userName; + } + + + @Override + public String toString() + { + return "User [userName=" + userName + ", displayName=" + displayName + "]"; + } +} diff --git a/source/java/org/alfresco/rest/api/nodes/NodeChildrenRelation.java b/source/java/org/alfresco/rest/api/nodes/NodeChildrenRelation.java index e982948641..723f7104e2 100644 --- a/source/java/org/alfresco/rest/api/nodes/NodeChildrenRelation.java +++ b/source/java/org/alfresco/rest/api/nodes/NodeChildrenRelation.java @@ -38,7 +38,7 @@ import java.util.List; * @author janv */ @RelationshipResource(name = "children", entityResource = NodesEntityResource.class, title = "Folder children") -public class NodeChildrenRelation implements RelationshipResourceAction.Read, RelationshipResourceAction.Create, InitializingBean +public class NodeChildrenRelation implements RelationshipResourceAction.Read, RelationshipResourceAction.Create, InitializingBean { private Nodes nodes; @@ -84,22 +84,22 @@ public class NodeChildrenRelation implements RelationshipResourceAction.Read create(String parentFolderNodeId, List folderInfos, Parameters parameters) + @WebApiDescription(title="Create one (or more) nodes as children of folder identified by parentFolderNodeId") + public List create(String parentFolderNodeId, List nodeInfos, Parameters parameters) { - List result = new ArrayList<>(folderInfos.size()); + List result = new ArrayList<>(nodeInfos.size()); - for (Folder folderInfo : folderInfos) + for (Node nodeInfo : nodeInfos) { - result.add(nodes.createFolder(parentFolderNodeId, folderInfo, parameters)); + result.add(nodes.createNode(parentFolderNodeId, nodeInfo, parameters)); } return result; diff --git a/source/java/org/alfresco/rest/api/nodes/NodesEntityResource.java b/source/java/org/alfresco/rest/api/nodes/NodesEntityResource.java index d9c03517c2..ae9c923e47 100644 --- a/source/java/org/alfresco/rest/api/nodes/NodesEntityResource.java +++ b/source/java/org/alfresco/rest/api/nodes/NodesEntityResource.java @@ -20,23 +20,32 @@ package org.alfresco.rest.api.nodes; import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.model.Node; +import org.alfresco.rest.framework.BinaryProperties; import org.alfresco.rest.framework.WebApiDescription; import org.alfresco.rest.framework.WebApiParam; +import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; import org.alfresco.rest.framework.resource.EntityResource; +import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction; import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction; +import org.alfresco.rest.framework.resource.content.BasicContentInfo; +import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.util.ParameterCheck; import org.springframework.beans.factory.InitializingBean; +import java.io.InputStream; + /** - * An implementation of an Entity Resource for a Node + * An implementation of an Entity Resource for a Node (file or folder) * * @author sglover * @author Gethin James * @author janv */ @EntityResource(name="nodes", title = "Nodes") -public class NodesEntityResource implements EntityResourceAction.ReadById, EntityResourceAction.Delete, EntityResourceAction.Update, InitializingBean +public class NodesEntityResource implements + EntityResourceAction.ReadById, EntityResourceAction.Delete, EntityResourceAction.Update, + BinaryResourceAction.Read, BinaryResourceAction.Update, InitializingBean { private Nodes nodes; @@ -54,13 +63,10 @@ public class NodesEntityResource implements EntityResourceAction.ReadById, /** * Returns information regarding the node 'nodeId' - folder or document * - * TODO other metadata/properties & permissions etc ... - * * @param nodeId String id of node (folder or document) - will also accept well-known aliases, eg. "-root-" or "-my-" * * Optional parameters: * - path - * - incPrimaryPath */ @WebApiDescription(title = "Get Node Information", description = "Get information for the node with id 'nodeId'") @WebApiParam(name = "nodeId", title = "The node id") @@ -69,15 +75,30 @@ public class NodesEntityResource implements EntityResourceAction.ReadById, return nodes.getFolderOrDocument(nodeId, parameters); } + @Override + @WebApiDescription(title = "Download content", description = "Download content") + @BinaryProperties({"content"}) + public BinaryResource readProperty(String fileNodeId, Parameters parameters) throws EntityNotFoundException + { + return nodes.getContent(fileNodeId, parameters); + } + + @Override + @WebApiDescription(title = "Upload content", description = "Upload content") + @BinaryProperties({"content"}) + public void update(String fileNodeId, BasicContentInfo contentInfo, InputStream stream, Parameters parameters) + { + nodes.updateContent(fileNodeId, contentInfo, stream, parameters); + } + /** * Update info on the node 'nodeId' - folder or document * - * Initially, name, title &/or description. Note: changing name is a "rename" (and must be unique within the current parent folder). - * - * TODO other metadata/properties & permissions etc ... + * Can update name (which is a "rename" and hence must be unique within the current parent folder) + * or update other properties. * * @param nodeId String nodeId of node (folder or document) - * @param nodeInfo node entity with info to update (eg. name, title, description ...) + * @param nodeInfo node entity with info to update (eg. name, properties ...) * @param parameters * @return */ diff --git a/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptPut.java b/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptPut.java index e418a4cd1d..905f217341 100644 --- a/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptPut.java +++ b/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptPut.java @@ -6,6 +6,7 @@ import java.util.Locale; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.web.scripts.BufferedRequest; import org.alfresco.rest.framework.core.ResourceInspector; import org.alfresco.rest.framework.core.ResourceLocator; import org.alfresco.rest.framework.core.ResourceMetadata; @@ -23,6 +24,7 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WrappingWebScriptRequest; import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; @@ -127,6 +129,14 @@ public class ResourceWebScriptPut extends AbstractResourceWebScript implements P logger.warn("Failed to get the input stream.", error); } } + else if (req instanceof WrappingWebScriptRequest) + { + // TODO review REST fwk change + // eg. BufferredRequest + WrappingWebScriptRequest wrappedRequest = (WrappingWebScriptRequest) req; + return wrappedRequest.getContent().getInputStream(); + } + return null; }