Merged HEAD (5.2) to 5.2.N (5.2.1)

126349 jkaabimofrad: Merged FILE-FOLDER-API (5.2.0) to HEAD (5.2)
      118692 jvonka: Merge from DEV/SABRE_JANV1 (part 2) - RA-613 / RA-655
      - File Folder API (PoC - experimental WIP)
      - TODO add tests +review backwards compat' (eg. favs)


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.2.N/root@126694 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Ancuta Morarasu
2016-05-11 10:44:49 +00:00
parent 3a5a22da8d
commit d3b503759b
12 changed files with 917 additions and 452 deletions

View File

@@ -430,13 +430,8 @@
</bean> </bean>
<bean id="nodes" class="org.alfresco.rest.api.impl.NodesImpl"> <bean id="nodes" class="org.alfresco.rest.api.impl.NodesImpl">
<property name="nodeService" ref="NodeService" /> <property name="serviceRegistry" ref="ServiceRegistry"/>
<property name="dictionaryService" ref="DictionaryService" /> <property name="repositoryHelper" ref="repositoryHelper"/>
<property name="cmisConnector" ref="CMISConnector" />
<property name="fileFolderService" ref="FileFolderService" />
<property name="repositoryHelper" ref="repositoryHelper" />
<property name="namespaceService" ref="NamespaceService" />
<property name="permissionService" ref="PermissionService" />
</bean> </bean>
<bean id="Nodes" class="org.springframework.aop.framework.ProxyFactoryBean"> <bean id="Nodes" class="org.springframework.aop.framework.ProxyFactoryBean">

View File

@@ -25,11 +25,14 @@
*/ */
package org.alfresco.rest.api; package org.alfresco.rest.api;
import java.io.InputStream;
import java.util.Set; import java.util.Set;
import org.alfresco.rest.api.model.Document; import org.alfresco.rest.api.model.Document;
import org.alfresco.rest.api.model.Folder; import org.alfresco.rest.api.model.Folder;
import org.alfresco.rest.api.model.Node; 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.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters; import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
@@ -96,13 +99,14 @@ public interface Nodes
void deleteNode(String nodeId); void deleteNode(String nodeId);
/** /**
* Create node(s) - folder or (empty) file
* *
* @param parentFolderNodeId * @param parentFolderNodeId
* @param folderInfo * @param nodeInfo
* @param parameters * @param parameters
* @return * @return
*/ */
Folder createFolder(String parentFolderNodeId, Folder folderInfo, Parameters parameters); Node createNode(String parentFolderNodeId, Node nodeInfo, Parameters parameters);
/** /**
* *
@@ -112,4 +116,10 @@ public interface Nodes
* @return * @return
*/ */
Node updateNode(String nodeId, Node entity, Parameters parameters); 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);
} }

View File

@@ -25,40 +25,57 @@
*/ */
package org.alfresco.rest.api.impl; 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.io.Serializable;
import java.util.AbstractList; import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; 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. * Centralises access to file/folder/node services and maps between representations.
* *
@@ -77,51 +94,86 @@ public class NodesImpl implements Nodes
private final static String PATH_ROOT = "-root-"; private final static String PATH_ROOT = "-root-";
private final static String PATH_MY = "-my-"; private final static String PATH_MY = "-my-";
private final static String PATH_SHARED = "-shared-";
private NodeService nodeService; private NodeService nodeService;
private DictionaryService dictionaryService; private DictionaryService dictionaryService;
private CMISConnector cmisConnector;
private FileFolderService fileFolderService; private FileFolderService fileFolderService;
private Repository repositoryHelper;
private NamespaceService namespaceService; private NamespaceService namespaceService;
private PermissionService permissionService; private PermissionService permissionService;
private Repository repositoryHelper;
private ServiceRegistry sr;
public void setDictionaryService(DictionaryService dictionaryService) public void setServiceRegistry(ServiceRegistry sr) {
{ this.sr = sr;
this.dictionaryService = dictionaryService;
}
public void setNodeService(NodeService nodeService) this.namespaceService = sr.getNamespaceService();
{ this.fileFolderService = sr.getFileFolderService();
this.nodeService = nodeService; this.nodeService = sr.getNodeService();
} this.permissionService = sr.getPermissionService();
this.dictionaryService = sr.getDictionaryService();
}
public void setCmisConnector(CMISConnector cmisConnector) public void setRepositoryHelper(Repository repositoryHelper)
{ {
this.cmisConnector = cmisConnector; this.repositoryHelper = repositoryHelper;
} }
public void setFileFolderService(FileFolderService fileFolderService) private static final List<QName> EXCLUDED_ASPECTS = Arrays.asList(
{ ContentModel.ASPECT_REFERENCEABLE,
this.fileFolderService = fileFolderService; ContentModel.ASPECT_LOCALIZED);
}
public void setRepositoryHelper(Repository repositoryHelper) private static final List<QName> EXCLUDED_PROPS = Arrays.asList(
{ // top-level basic info
this.repositoryHelper = repositoryHelper; 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);
public void setNamespaceService(NamespaceService namespaceService) private final static String PARAM_ISFOLDER = "isFolder";
{ private final static String PARAM_NAME = "name";
this.namespaceService = namespaceService; 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";
public void setPermissionService(PermissionService permissionService) private final static Map<String,QName> MAP_PARAM_QNAME;
{ static {
this.permissionService = permissionService; Map<String,QName> 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<String> LIST_FOLDER_CHILDREN_EQUALS_QUERY_PROPERTIES =
new HashSet<>(Arrays.asList(new String[] {PARAM_ISFOLDER}));
/*
* *
* Note: assumes workspace://SpacesStore * Note: assumes workspace://SpacesStore
*/ */
@@ -152,7 +204,7 @@ public class NodesImpl implements Nodes
public NodeRef validateNode(NodeRef nodeRef) public NodeRef validateNode(NodeRef nodeRef)
{ {
if(!nodeService.exists(nodeRef)) if (! nodeService.exists(nodeRef))
{ {
throw new EntityNotFoundException(nodeRef.getId()); throw new EntityNotFoundException(nodeRef.getId());
} }
@@ -161,27 +213,30 @@ public class NodesImpl implements Nodes
} }
public boolean nodeMatches(NodeRef nodeRef, Set<QName> expectedTypes, Set<QName> excludedTypes) public boolean nodeMatches(NodeRef nodeRef, Set<QName> expectedTypes, Set<QName> excludedTypes)
{ {
if(!nodeService.exists(nodeRef)) if (! nodeService.exists(nodeRef))
{ {
throw new EntityNotFoundException(nodeRef.getId()); throw new EntityNotFoundException(nodeRef.getId());
} }
QName type = nodeService.getType(nodeRef); return typeMatches(nodeService.getType(nodeRef), expectedTypes, excludedTypes);
}
Set<QName> allExpectedTypes = new HashSet<QName>(); protected boolean typeMatches(QName type, Set<QName> expectedTypes, Set<QName> excludedTypes)
if(expectedTypes != null) {
Set<QName> allExpectedTypes = new HashSet<>();
if (expectedTypes != null)
{ {
for(QName expectedType : expectedTypes) for (QName expectedType : expectedTypes)
{ {
allExpectedTypes.addAll(dictionaryService.getSubTypes(expectedType, true)); allExpectedTypes.addAll(dictionaryService.getSubTypes(expectedType, true));
} }
} }
Set<QName> allExcludedTypes = new HashSet<QName>(); Set<QName> allExcludedTypes = new HashSet<>();
if(excludedTypes != null) if (excludedTypes != null)
{ {
for(QName excludedType : excludedTypes) for (QName excludedType : excludedTypes)
{ {
allExcludedTypes.addAll(dictionaryService.getSubTypes(excludedType, true)); allExcludedTypes.addAll(dictionaryService.getSubTypes(excludedType, true));
} }
@@ -192,16 +247,22 @@ public class NodesImpl implements Nodes
return(inExpected && !excluded); return(inExpected && !excluded);
} }
/**
* @deprecated review usage (backward compat')
*/
public Node getNode(String nodeId) public Node getNode(String nodeId)
{ {
NodeRef nodeRef = validateNode(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) 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) private Type getType(NodeRef nodeRef)
@@ -216,65 +277,19 @@ public class NodesImpl implements Nodes
return isContainer ? Type.FOLDER : Type.DOCUMENT; 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<CmisExtensionElement> getExtensions()
{
return properties.getExtensions();
}
@Override
public void setExtensions(List<CmisExtensionElement> extensions)
{
properties.setExtensions(extensions);
}
@Override
public Map<String, PropertyData<?>> getProperties()
{
Map<String, PropertyData<?>> updatedProperties = new HashMap<String, PropertyData<?>>(properties.getProperties());
updatedProperties.put(titleProp.getId(), titleProp);
return updatedProperties;
}
@Override
public List<PropertyData<?>> getPropertyList()
{
List<PropertyData<?>> propertyList = new ArrayList<PropertyData<?>>(properties.getPropertyList());
propertyList.add(titleProp);
return propertyList;
}
};
return wrapProperties;
}
*/
/** /**
* Returns the public api representation of a document. * 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) public Document getDocument(NodeRef nodeRef)
{ {
Type type = getType(nodeRef); Type type = getType(nodeRef);
if (type.equals(Type.DOCUMENT)) if (type.equals(Type.DOCUMENT))
{ {
//Properties properties = getCMISProperties(nodeRef);
Map<QName, Serializable> properties = nodeService.getProperties(nodeRef); Map<QName, Serializable> properties = nodeService.getProperties(nodeRef);
Document doc = new Document(nodeRef, properties, namespaceService);
Document doc = new Document(nodeRef, getParentNodeRef(nodeRef), properties, sr);
return doc; return doc;
} }
else else
@@ -286,16 +301,16 @@ public class NodesImpl implements Nodes
/** /**
* Returns the public api representation of a folder. * 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) public Folder getFolder(NodeRef nodeRef)
{ {
Type type = getType(nodeRef); Type type = getType(nodeRef);
if (type.equals(Type.FOLDER)) if (type.equals(Type.FOLDER))
{ {
//Properties properties = getCMISProperties(nodeRef);
Map<QName, Serializable> properties = nodeService.getProperties(nodeRef); Map<QName, Serializable> properties = nodeService.getProperties(nodeRef);
Folder folder = new Folder(nodeRef, properties, namespaceService);
Folder folder = new Folder(nodeRef, getParentNodeRef(nodeRef), properties, sr);
return folder; return folder;
} }
else else
@@ -304,12 +319,27 @@ public class NodesImpl implements Nodes
} }
} }
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) { private NodeRef validateOrLookupNode(String nodeId, String path) {
final NodeRef parentNodeRef; NodeRef parentNodeRef;
if (nodeId.equals(PATH_ROOT)) if (nodeId.equals(PATH_ROOT))
{ {
parentNodeRef = repositoryHelper.getCompanyHome(); parentNodeRef = repositoryHelper.getCompanyHome();
} }
else if (nodeId.equals(PATH_SHARED))
{
parentNodeRef = repositoryHelper.getSharedHome();
}
else if (nodeId.equals(PATH_MY)) else if (nodeId.equals(PATH_MY))
{ {
NodeRef person = repositoryHelper.getPerson(); NodeRef person = repositoryHelper.getPerson();
@@ -323,103 +353,272 @@ public class NodesImpl implements Nodes
{ {
parentNodeRef = validateNode(nodeId); parentNodeRef = validateNode(nodeId);
} }
if (path != null) {
// resolve path relative to current nodeId
parentNodeRef = resolveNodeByPath(parentNodeRef, path, true);
}
return parentNodeRef; return parentNodeRef;
} }
protected NodeRef resolveNodeByPath(final NodeRef parentNodeRef, String path, boolean checkForCompanyHome)
{
final List<String> 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) public Node getFolderOrDocument(String nodeId, Parameters parameters)
{ {
String path = parameters.getParameter("path"); 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); NodeRef nodeRef = validateOrLookupNode(nodeId, path);
QName typeQName = nodeService.getType(nodeRef); 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; PathInfo pathInfo = null;
if (incPrimaryPath) if (! minimalnfo)
{ {
org.alfresco.service.cmr.repository.Path pp = nodeService.getPath(nodeRef); pathInfo = lookupPathInfo(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)
} }
Node node = null;
Type type = getType(typeQName); Type type = getType(typeQName);
Node node;
Map<QName, Serializable> properties = nodeService.getProperties(nodeRef); Map<QName, Serializable> properties = nodeService.getProperties(nodeRef);
if (type.equals(Type.DOCUMENT)) if (type.equals(Type.DOCUMENT))
{ {
//Properties properties = getCMISProperties(nodeRef); node = new Document(nodeRef, parentNodeRef, properties, sr);
node = new Document(nodeRef, properties, namespaceService);
} }
else if (type.equals(Type.FOLDER)) else if (type.equals(Type.FOLDER))
{ {
// container/folder // container/folder
//Properties properties = getCMISProperties(nodeRef); node = new Folder(nodeRef, parentNodeRef, properties, sr);
node = new Folder(nodeRef, properties, namespaceService);
} }
else else
{ {
throw new InvalidArgumentException("Node is not a folder or file"); throw new InvalidArgumentException("Node is not a folder or file");
} }
node.setType(typeQName.toPrefixString(namespaceService)); if (! minimalnfo) {
node.setPrimaryPath(primaryPath); // optional - can be null node.setProperties(mapProperties(properties));
node.setAspectNames(mapAspects(nodeService.getAspects(nodeRef)));
}
node.setNodeType(typeQName.toPrefixString(namespaceService));
node.setPath(pathInfo);
return node; return node;
} }
protected PathInfo lookupPathInfo(NodeRef nodeRefIn)
{
List<PathInfo.ElementInfo> 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<String, Serializable> mapProperties(Map<QName, Serializable> nodeProps)
{
Map<String, Serializable> props = new HashMap<>(nodeProps.size());
for (Map.Entry<QName, Serializable> 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<String> mapAspects(Set<QName> nodeAspects)
{
List<String> 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<Node> getChildren(String parentFolderNodeId, Parameters parameters) public CollectionWithPagingInfo<Node> getChildren(String parentFolderNodeId, Parameters parameters)
{ {
// TODO consider using: where=(exists(target/file)) / where=(exists(target/file)) // TODO
// instead of: includeFiles=true / includeFolders=true // map - where (filter) properties - including isFolder
// map - orderBy (sort) properties - including isFolder
boolean includeFolders = true; // TODO refactor & fix !
String str = parameters.getParameter("includeFolders"); final boolean minimalnfo = (parameters.getSelectedProperties().size() == 0);
if (str != null) {
includeFolders = new Boolean(str); 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; List<SortColumn> sortCols = parameters.getSorting();
str = parameters.getParameter("includeFiles"); List<Pair<QName, Boolean>> sortProps = null;
if (str != null) { if ((sortCols != null) && (sortCols.size() > 0))
includeFiles = new Boolean(str); {
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));
}
}
}
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)));
} }
String path = parameters.getParameter("path"); Paging paging = parameters.getPaging();
// TODO do we want to support path with list folder children ?
String path = null;
//String path = parameters.getParameter("path");
Paging paging = parameters.getPaging();
final NodeRef parentNodeRef = validateOrLookupNode(parentFolderNodeId, path); final NodeRef parentNodeRef = validateOrLookupNode(parentFolderNodeId, path);
final Set<QName> folders = new HashSet<>(Arrays.asList(ContentModel.TYPE_FOLDER)); if (! nodeMatches(parentNodeRef, Collections.singleton(ContentModel.TYPE_FOLDER), null))
if (! nodeMatches(parentNodeRef, folders, null))
{ {
throw new InvalidArgumentException("NodeId of folder is expected"); throw new InvalidArgumentException("NodeId of folder is expected");
} }
PagingRequest pagingRequest = Util.getPagingRequest(paging); PagingRequest pagingRequest = Util.getPagingRequest(paging);
final PagingResults<FileInfo> pagingResults = fileFolderService.list(parentNodeRef, includeFiles, includeFolders, null, null, pagingRequest); final PagingResults<FileInfo> pagingResults = fileFolderService.list(parentNodeRef, includeFiles, includeFolders, null, sortProps, pagingRequest);
final List<FileInfo> page = pagingResults.getPage(); final List<FileInfo> page = pagingResults.getPage();
List<Node> nodes = new AbstractList<Node>() List<Node> nodes = new AbstractList<Node>()
@@ -428,7 +627,9 @@ public class NodesImpl implements Nodes
public Node get(int index) public Node get(int index)
{ {
FileInfo fInfo = page.get(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 @Override
@@ -447,48 +648,68 @@ public class NodesImpl implements Nodes
fileFolderService.delete(nodeRef); 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<QName> folders = new HashSet<>(Arrays.asList(ContentModel.TYPE_FOLDER)); if (! nodeMatches(parentNodeRef, Collections.singleton(ContentModel.TYPE_FOLDER), null))
if (! nodeMatches(parentNodeRef, folders, null))
{ {
throw new InvalidArgumentException("NodeId of folder is expected"); throw new InvalidArgumentException("NodeId of folder is expected: "+parentNodeRef);
} }
String folderName = folderInfo.getName(); String nodeName = nodeInfo.getName();
String folderType = folderInfo.getType(); if ((nodeName == null) || nodeName.isEmpty())
if (folderType == null) { {
folderType = "cm:folder"; throw new InvalidArgumentException("Node name is expected: "+parentNodeRef);
}
QName folderTypeQName = QName.resolveToQName(namespaceService, folderType);
Map<QName, Serializable> props = new HashMap<QName, Serializable>(10);
props.put(ContentModel.PROP_NAME, folderName);
String title = folderInfo.getTitle();
if ((title != null) && (! title.isEmpty())) {
props.put(ContentModel.PROP_TITLE, title);
} }
String description = folderInfo.getDescription(); String nodeType = nodeInfo.getNodeType();
if ((description != null) && (! description.isEmpty())) { if ((nodeType == null) || nodeType.isEmpty())
props.put(ContentModel.PROP_DESCRIPTION, description); {
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<QName> 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<QName, Serializable> props = new HashMap<>(10);
if (nodeInfo.getProperties() != null)
{
for (Map.Entry entry : (Set<Map.Entry<String, Serializable>>)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); final NodeRef nodeRef = validateNode(nodeId);
@@ -500,26 +721,72 @@ public class NodesImpl implements Nodes
Map<QName, Serializable> props = new HashMap<>(10); Map<QName, Serializable> props = new HashMap<>(10);
if (nodeInfo.getProperties() != null)
{
for (Map.Entry entry : (Set<Map.Entry<String, Serializable>>)nodeInfo.getProperties().entrySet())
{
QName propQName = QName.createQName((String)entry.getKey(), namespaceService);
props.put(propQName, (Serializable)entry.getValue());
}
}
String name = nodeInfo.getName(); String name = nodeInfo.getName();
if ((name != null) && (! name.isEmpty())) { if ((name != null) && (! name.isEmpty()))
{
// note: this is equivalent of a rename within target folder // note: this is equivalent of a rename within target folder
props.put(ContentModel.PROP_NAME, name); props.put(ContentModel.PROP_NAME, name);
} }
String title = nodeInfo.getTitle(); if (props.size() > 0)
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) {
nodeService.addProperties(nodeRef, props); nodeService.addProperties(nodeRef, props);
} }
return getFolderOrDocument(nodeRef.getId(), parameters); 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;
}
} }

View File

@@ -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 + "]";
}
}

View File

@@ -29,12 +29,11 @@ import java.io.Serializable;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Map; 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.cmr.repository.NodeRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName; 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. * Representation of a document node.
@@ -45,31 +44,32 @@ import org.apache.chemistry.opencmis.commons.data.PropertyData;
*/ */
public class Document extends Node public class Document extends Node
{ {
// TODO backward compat' - favourites etc
private String mimeType; private String mimeType;
private BigInteger sizeInBytes; private BigInteger sizeInBytes;
private String versionLabel; private String versionLabel;
public Document() private ContentInfo contentInfo;
{
super();
}
/* public Document() {
public Document(NodeRef nodeRef, Properties properties) super();
{ }
super(nodeRef, properties);
Map<String, PropertyData<?>> props = properties.getProperties(); public Document(NodeRef nodeRef, NodeRef parentNodeRef, Map<QName, Serializable> nodeProps, ServiceRegistry sr)
this.mimeType = (String)getValue(props, PropertyIds.CONTENT_STREAM_MIME_TYPE); {
this.sizeInBytes = (BigInteger)getValue(props, PropertyIds.CONTENT_STREAM_LENGTH); super(nodeRef, parentNodeRef, nodeProps, sr);
this.versionLabel = (String)getValue(props, PropertyIds.VERSION_LABEL);
}
*/
public Document(NodeRef nodeRef, Map<QName, Serializable> nodeProps, NamespaceService namespaceService) Serializable val = nodeProps.get(ContentModel.PROP_CONTENT);
{
super(nodeRef, nodeProps, namespaceService); 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() public String getMimeType()
{ {
@@ -91,13 +91,16 @@ public class Document extends Node
return false; return false;
} }
public ContentInfo getContent()
{
return contentInfo;
}
@Override @Override
public String toString() public String toString()
{ {
return "Document [mimeType=" + mimeType + ", sizeInBytes=" return "Document [contentInfo=" + contentInfo.toString() + ", nodeRef="
+ sizeInBytes + ", versionLabel=" + versionLabel + ", nodeRef=" + nodeRef + ", name=" + name + ", createdAt=" + createdAt
+ nodeRef + ", name=" + name + ", title=" + title
+ ", description=" + description + ", createdAt=" + createdAt
+ ", modifiedAt=" + modifiedAt + ", createdBy=" + createdBy + ", modifiedAt=" + modifiedAt + ", createdBy=" + createdBy
+ ", modifiedBy=" + modifiedBy + "]"; + ", modifiedBy=" + modifiedBy + "]";
} }

View File

@@ -28,10 +28,9 @@ package org.alfresco.rest.api.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map; import java.util.Map;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.apache.chemistry.opencmis.commons.data.Properties;
/** /**
* Representation of a folder node. * Representation of a folder node.
@@ -47,17 +46,10 @@ public class Folder extends Node
super(); super();
} }
/* public Folder(NodeRef nodeRef, NodeRef parentNodeRef, Map<QName, Serializable> nodeProps, ServiceRegistry sr)
public Folder(NodeRef nodeRef, Properties properties) {
{ super(nodeRef, parentNodeRef, nodeProps, sr);
super(nodeRef, properties); }
}
*/
public Folder(NodeRef nodeRef, Map<QName, Serializable> nodeProps, NamespaceService namespaceService)
{
super(nodeRef, nodeProps, namespaceService);
}
public Boolean getIsFolder() public Boolean getIsFolder()
{ {

View File

@@ -26,23 +26,18 @@
package org.alfresco.rest.api.model; package org.alfresco.rest.api.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.rest.framework.resource.UniqueId; import org.alfresco.rest.framework.resource.UniqueId;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef; 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.service.namespace.QName;
import org.alfresco.util.EqualsHelper; 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; import org.apache.chemistry.opencmis.commons.data.PropertyData;
/** /**
@@ -54,139 +49,116 @@ import org.apache.chemistry.opencmis.commons.data.PropertyData;
*/ */
public class Node implements Comparable<Node> public class Node implements Comparable<Node>
{ {
protected NodeRef nodeRef; protected NodeRef nodeRef;
protected String name; protected String name;
protected String title;
protected NodeRef guid; // TODO review - do we need for favorites (backwards compat') ? // TODO needed for favourties - backwards compat' - we could also choose to split of NodeInfo / Node impl's etc
protected String description; protected String title;
protected Date createdAt; protected NodeRef guid;
protected Date modifiedAt; protected String description;
protected String createdBy; protected String createdBy;
protected String modifiedBy; 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 String prefixTypeQName;
protected List<String> aspectNames;
protected Map<String, Serializable> props; protected Map<String, Serializable> props;
private static final List<QName> 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 ! // TODO fixme !
public Node(NodeRef nodeRef, Map<QName, Serializable> 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<QName, Serializable> nodeProps, ServiceRegistry sr)
{ {
if(nodeRef == null) if(nodeRef == null)
{ {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
this.nodeRef = nodeRef; this.nodeRef = nodeRef;
mapProperties(nodeProps, namespaceService); this.parentNodeRef = parentNodeRef;
mapBasicInfo(nodeProps, sr);
} }
protected Object getValue(Map<String, PropertyData<?>> props, String name) protected Object getValue(Map<String, PropertyData<?>> props, String name)
{ {
PropertyData<?> prop = props.get(name); PropertyData<?> prop = props.get(name);
Object value = (prop != null ? prop.getFirstValue() : null); Object value = (prop != null ? prop.getFirstValue() : null);
return value; return value;
} }
/*
public Node(NodeRef nodeRef, Properties properties)
{
this.nodeRef = nodeRef;
Map<String, PropertyData<?>> 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);
}
*/
public Node() public Node()
{ {
} }
protected void mapProperties(Map<QName, Serializable> nodeProps, NamespaceService namespaceService) protected void mapBasicInfo(Map<QName, Serializable> nodeProps, ServiceRegistry sr)
{ {
PersonService personService = sr.getPersonService();
// TODO review backwards compat' for favorites & others (eg. set guid explicitly where still needed) // TODO review backwards compat' for favorites & others (eg. set guid explicitly where still needed)
//this.guid = nodeRef; //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.name = (String)nodeProps.get(ContentModel.PROP_NAME);
this.title = (String)nodeProps.get(ContentModel.PROP_TITLE);
this.description = (String)nodeProps.get(ContentModel.PROP_DESCRIPTION);
this.createdAt = (Date)nodeProps.get(ContentModel.PROP_CREATED); this.createdAt = (Date)nodeProps.get(ContentModel.PROP_CREATED);
this.createdBy = (String)nodeProps.get(ContentModel.PROP_CREATOR); this.createdByUser = lookupUserInfo((String)nodeProps.get(ContentModel.PROP_CREATOR), personService);
this.modifiedAt = (Date)nodeProps.get(ContentModel.PROP_MODIFIED);
this.modifiedBy = (String)nodeProps.get(ContentModel.PROP_MODIFIER);
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<QName, Serializable> entry : nodeProps.entrySet()) { // TODO refactor & optimise to avoid multiple person lookups
QName propQName = entry.getKey(); private UserInfo lookupUserInfo(final String userName, final PersonService personService) {
if (! EXCLUDED_PROPS.contains(propQName)) {
props.put(entry.getKey().toPrefixString(namespaceService), entry.getValue()); 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) public void setGuid(NodeRef guid)
{ {
this.guid = guid; this.guid = guid;
} }
public NodeRef getGuid() public NodeRef getGuid() {
{ return guid;
return guid; }
}
public String getTitle() public String getTitle()
{ {
return title; return title;
} }
@UniqueId @UniqueId
public NodeRef getNodeRef() public NodeRef getNodeRef()
{ {
return nodeRef; return nodeRef;
} }
public void setNodeRef(NodeRef nodeRef) public void setNodeRef(NodeRef nodeRef)
{ {
// if(nodeRef == null) this.nodeRef = nodeRef;
// { }
// throw new IllegalArgumentException();
// }
this.nodeRef = nodeRef;
}
public Date getCreatedAt() public Date getCreatedAt()
{ {
return this.createdAt; return this.createdAt;
} }
@@ -196,20 +168,28 @@ public class Node implements Comparable<Node>
this.createdAt = createdAt; this.createdAt = createdAt;
} }
public Date getModifiedAt() public Date getModifiedAt()
{ {
return modifiedAt; return modifiedAt;
} }
public String getModifiedBy() public String getModifiedBy()
{ {
return modifiedBy; return modifiedBy;
} }
public UserInfo getModifiedByUser() {
return modifiedByUser;
}
public UserInfo getCreatedByUser() {
return createdByUser;
}
public String getDescription() public String getDescription()
{ {
return description; return description;
} }
public String getName() public String getName()
{ {
@@ -231,58 +211,75 @@ public class Node implements Comparable<Node>
this.createdBy = createdBy; this.createdBy = createdBy;
} }
public String getPrimaryPath() public PathInfo getPath()
{
return primaryPath;
}
public void setPrimaryPath(String primaryPath)
{ {
this.primaryPath = primaryPath; return pathInfo;
} }
public String getType() public void setPath(PathInfo pathInfo)
{
return prefixTypeQName;
}
public void setType(String prefixType)
{ {
this.prefixTypeQName = prefixType; this.pathInfo = pathInfo;
} }
public Map getProperties() { public String getNodeType()
{
return prefixTypeQName;
}
public void setNodeType(String prefixType)
{
this.prefixTypeQName = prefixType;
}
public Map getProperties() {
return this.props; return this.props;
} }
public boolean equals(Object other) public void setProperties(Map props) {
{ this.props = props;
if(this == other) }
{
return true;
}
if(!(other instanceof Node)) public List<String> getAspectNames() {
{ return aspectNames;
return false; }
}
Node node = (Node)other; public void setAspectNames(List<String> aspectNames) {
return EqualsHelper.nullSafeEquals(getNodeRef(), node.getNodeRef()); this.aspectNames = aspectNames;
} }
@Override public NodeRef getParentId()
public int compareTo(Node node) {
{ return parentNodeRef;
return getNodeRef().toString().compareTo(node.getNodeRef().toString()); }
}
@Override public boolean equals(Object other)
public String toString() {
{ if(this == other)
return "Node [nodeRef=" + nodeRef + ", type=" + prefixTypeQName + ", name=" + name + ", title=" {
+ title + ", description=" + description + ", createdAt=" return true;
+ createdAt + ", modifiedAt=" + modifiedAt + ", createdBy=" + createdBy + ", modifiedBy=" }
+ modifiedBy + ", primaryPath =" + primaryPath +"]";
} 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 +"]";
}
} }

View File

@@ -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<ElementInfo> elements;
public PathInfo()
{
}
public PathInfo(String name, Boolean isComplete, List<ElementInfo> elements)
{
this.name = name;
this.isComplete = isComplete;
this.elements = elements;
}
public String getName() {
return name;
}
public Boolean getIsComplete() {
return isComplete;
}
public List<ElementInfo> 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;
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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 + "]";
}
}

View File

@@ -38,7 +38,7 @@ import java.util.List;
* @author janv * @author janv
*/ */
@RelationshipResource(name = "children", entityResource = NodesEntityResource.class, title = "Folder children") @RelationshipResource(name = "children", entityResource = NodesEntityResource.class, title = "Folder children")
public class NodeChildrenRelation implements RelationshipResourceAction.Read<Node>, RelationshipResourceAction.Create<Folder>, InitializingBean public class NodeChildrenRelation implements RelationshipResourceAction.Read<Node>, RelationshipResourceAction.Create<Node>, InitializingBean
{ {
private Nodes nodes; private Nodes nodes;
@@ -84,22 +84,22 @@ public class NodeChildrenRelation implements RelationshipResourceAction.Read<Nod
} }
/** /**
* Create one or more sub-folders below parent folder. Note: can also use well-known alias, eg. -root- or -my- * Create one or more nodes (folder or empty file) below parent folder.
* *
* TODO also consider option to create path - eg. by passing name path in name (see Sparta API as an example) ... or should this be a separate API ? * Note: for parent folder nodeId, can also use well-known alias, eg. -root- or -my-
* *
* If parentFolderNodeId does not exist, EntityNotFoundException (status 404). * If parentFolderNodeId does not exist, EntityNotFoundException (status 404).
* If parentFolderNodeId does not represent a folder, InvalidArgumentException (status 400). * If parentFolderNodeId does not represent a folder, InvalidArgumentException (status 400).
*/ */
@Override @Override
@WebApiDescription(title="Create one (or more) folder(s) as a child of folder identified by parentFolderNodeId") @WebApiDescription(title="Create one (or more) nodes as children of folder identified by parentFolderNodeId")
public List<Folder> create(String parentFolderNodeId, List<Folder> folderInfos, Parameters parameters) public List<Node> create(String parentFolderNodeId, List<Node> nodeInfos, Parameters parameters)
{ {
List<Folder> result = new ArrayList<>(folderInfos.size()); List<Node> 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; return result;

View File

@@ -27,23 +27,32 @@ package org.alfresco.rest.api.nodes;
import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.model.Node; import org.alfresco.rest.api.model.Node;
import org.alfresco.rest.framework.BinaryProperties;
import org.alfresco.rest.framework.WebApiDescription; import org.alfresco.rest.framework.WebApiDescription;
import org.alfresco.rest.framework.WebApiParam; 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.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.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.rest.framework.resource.parameters.Parameters;
import org.alfresco.util.ParameterCheck; import org.alfresco.util.ParameterCheck;
import org.springframework.beans.factory.InitializingBean; 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 sglover
* @author Gethin James * @author Gethin James
* @author janv * @author janv
*/ */
@EntityResource(name="nodes", title = "Nodes") @EntityResource(name="nodes", title = "Nodes")
public class NodesEntityResource implements EntityResourceAction.ReadById<Node>, EntityResourceAction.Delete, EntityResourceAction.Update<Node>, InitializingBean public class NodesEntityResource implements
EntityResourceAction.ReadById<Node>, EntityResourceAction.Delete, EntityResourceAction.Update<Node>,
BinaryResourceAction.Read, BinaryResourceAction.Update, InitializingBean
{ {
private Nodes nodes; private Nodes nodes;
@@ -61,13 +70,10 @@ public class NodesEntityResource implements EntityResourceAction.ReadById<Node>,
/** /**
* Returns information regarding the node 'nodeId' - folder or document * 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-" * @param nodeId String id of node (folder or document) - will also accept well-known aliases, eg. "-root-" or "-my-"
* *
* Optional parameters: * Optional parameters:
* - path * - path
* - incPrimaryPath
*/ */
@WebApiDescription(title = "Get Node Information", description = "Get information for the node with id 'nodeId'") @WebApiDescription(title = "Get Node Information", description = "Get information for the node with id 'nodeId'")
@WebApiParam(name = "nodeId", title = "The node id") @WebApiParam(name = "nodeId", title = "The node id")
@@ -76,15 +82,30 @@ public class NodesEntityResource implements EntityResourceAction.ReadById<Node>,
return nodes.getFolderOrDocument(nodeId, parameters); 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 * 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). * Can update name (which is a "rename" and hence must be unique within the current parent folder)
* * or update other properties.
* TODO other metadata/properties & permissions etc ...
* *
* @param nodeId String nodeId of node (folder or document) * @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 * @param parameters
* @return * @return
*/ */

View File

@@ -31,6 +31,7 @@ import java.util.Locale;
import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; 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.ResourceInspector;
import org.alfresco.rest.framework.core.ResourceLocator; import org.alfresco.rest.framework.core.ResourceLocator;
import org.alfresco.rest.framework.core.ResourceMetadata; import org.alfresco.rest.framework.core.ResourceMetadata;
@@ -48,6 +49,7 @@ import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.webscripts.WebScriptRequest; import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WrappingWebScriptRequest;
import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest; import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -152,6 +154,14 @@ public class ResourceWebScriptPut extends AbstractResourceWebScript implements P
logger.warn("Failed to get the input stream.", error); 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; return null;
} }