diff --git a/source/java/org/alfresco/rest/api/impl/NodesImpl.java b/source/java/org/alfresco/rest/api/impl/NodesImpl.java index 8192aed147..5d68bc2b6e 100644 --- a/source/java/org/alfresco/rest/api/impl/NodesImpl.java +++ b/source/java/org/alfresco/rest/api/impl/NodesImpl.java @@ -1285,7 +1285,7 @@ public class NodesImpl implements Nodes } // check that requested parent node exists and it's type is a (sub-)type of folder - final NodeRef parentNodeRef = validateOrLookupNode(parentFolderNodeId, null); + NodeRef parentNodeRef = validateOrLookupNode(parentFolderNodeId, null); if (! nodeMatches(parentNodeRef, Collections.singleton(ContentModel.TYPE_FOLDER), null, false)) { @@ -1323,7 +1323,7 @@ public class NodesImpl implements Nodes } // Existing file/folder name handling - final boolean autoRename = Boolean.valueOf(parameters.getParameter(PARAM_AUTO_RENAME)); + boolean autoRename = Boolean.valueOf(parameters.getParameter(PARAM_AUTO_RENAME)); if (autoRename && (isContent || isSubClass(nodeTypeQName, ContentModel.TYPE_FOLDER))) { NodeRef existingNode = nodeService.getChildByName(parentNodeRef, ContentModel.ASSOC_CONTAINS, nodeName); @@ -1334,6 +1334,9 @@ public class NodesImpl implements Nodes } } + String relativePath = nodeInfo.getRelativePath(); + parentNodeRef = getOrCreatePath(parentNodeRef, relativePath); + // Create the node NodeRef nodeRef = createNodeImpl(parentNodeRef, nodeName, nodeTypeQName, props); @@ -1364,6 +1367,23 @@ public class NodesImpl implements Nodes return getFolderOrDocument(nodeRef.getId(), parameters); } + private NodeRef getOrCreatePath(NodeRef parentNodeRef, String relativePath) + { + if (relativePath != null) + { + List pathElements = getPathElements(relativePath); + + // Checks for the presence of, and creates as necessary, + // the folder structure in the provided path elements list. + if (pathElements != null && !pathElements.isEmpty()) + { + parentNodeRef = makeFolders(parentNodeRef, pathElements); + } + } + + return parentNodeRef; + } + private NodeRef createNodeImpl(NodeRef parentNodeRef, String nodeName, QName nodeTypeQName, Map props) { if (props == null) @@ -1842,7 +1862,7 @@ public class NodesImpl implements Nodes boolean overwrite = false; // If a fileName clashes for a versionable file Boolean majorVersion = null; String versionComment = null; - List pathElements = null; + String relativePath = null; Map qnameStrProps = new HashMap<>(); Map properties = null; @@ -1887,7 +1907,7 @@ public class NodesImpl implements Nodes break; case "relativepath": - pathElements = getPathElements(getStringOrNull(field.getValue())); + relativePath = getStringOrNull(field.getValue()); break; default: @@ -1919,12 +1939,8 @@ public class NodesImpl implements Nodes throw new InvalidArgumentException("Both 'overwrite' and 'autoRename' should not be true when uploading a file"); } - // Checks for the presence of, and creates as necessary, - // the folder structure in the provided path elements list. - if (pathElements != null && !pathElements.isEmpty()) - { - parentNodeRef = makeFolders(parentNodeRef, pathElements); - } + // if requested, make (get or create) path + parentNodeRef = getOrCreatePath(parentNodeRef, relativePath); try { @@ -1945,6 +1961,8 @@ public class NodesImpl implements Nodes { // attempt to find a unique name fileName = findUniqueName(parentNodeRef, fileName); + + // drop-through ! } else if (overwrite && nodeService.hasAspect(existingFile, ContentModel.ASPECT_VERSIONABLE)) { @@ -1994,9 +2012,6 @@ public class NodesImpl implements Nodes } } - /** - * Helper to create a new node and writes its content to the repository. - */ private Node createNewFile(NodeRef parentNodeRef, String fileName, QName nodeType, Content content, Map props, Parameters params) { if (nodeType == null) diff --git a/source/java/org/alfresco/rest/api/model/Node.java b/source/java/org/alfresco/rest/api/model/Node.java index ad081366d4..2808b019d8 100644 --- a/source/java/org/alfresco/rest/api/model/Node.java +++ b/source/java/org/alfresco/rest/api/model/Node.java @@ -71,6 +71,9 @@ public class Node implements Comparable protected PathInfo pathInfo; protected String prefixTypeQName; + // note: currently only used for create request + protected String relativePath; + protected List aspectNames; protected Map properties; @@ -297,6 +300,16 @@ public class Node implements Comparable this.allowableOperations = allowableOperations; } + public String getRelativePath() + { + return relativePath; + } + + public void setRelativePath(String relativePath) + { + this.relativePath = relativePath; + } + public boolean equals(Object other) { if(this == other) @@ -329,16 +342,38 @@ public class Node implements Comparable sb.append(", name=").append(getName()); sb.append(", isFolder=").append(getIsFolder()); sb.append(", isFile=").append(getIsFile()); - sb.append(", isLink=").append(getIsLink()); // note: symbolic link (not shared link) sb.append(", modifiedAt=").append(getModifiedAt()); sb.append(", modifiedByUser=").append(getModifiedByUser()); sb.append(", createdAt=").append(getCreatedAt()); sb.append(", createdByUser=").append(getCreatedByUser()); - sb.append(", path=").append(getPath()); - sb.append(", content=").append(getContent()); - sb.append(", aspectNames=").append(getAspectNames()); - //sb.append(", properties=").append(getProperties()); - sb.append(", allowableOperations=").append(getAllowableOperations()); + if (getIsLink() != null) + { + sb.append(", isLink=").append(getIsLink()); // note: symbolic link (not shared link) + } + if (getPath() != null) + { + sb.append(", path=").append(getPath()); + } + if (getContent() != null) + { + sb.append(", content=").append(getContent()); + } + if (getAspectNames() != null) + { + sb.append(", aspectNames=").append(getAspectNames()); + } + if (getProperties() != null) + { + //sb.append(", properties=").append(getProperties()); + } + if (getRelativePath() != null) + { + sb.append(", relativePath=").append(getRelativePath()); + } + if (getAllowableOperations() != null) + { + sb.append(", allowableOperations=").append(getAllowableOperations()); + } sb.append("]"); return sb.toString(); } diff --git a/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java b/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java index 0032f644a8..693d51f3bf 100644 --- a/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java +++ b/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java @@ -697,12 +697,12 @@ public class NodeApiTest extends AbstractBaseApiTest // get node info via relativePath - params = Collections.singletonMap("relativePath", "/"+folderA+"/"+folderB); + params = Collections.singletonMap(Nodes.PARAM_RELATIVE_PATH, "/"+folderA+"/"+folderB); response = getSingle(NodesEntityResource.class, user1, Nodes.PATH_MY, params, 200); Folder folderResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Folder.class); assertEquals(folderB_Id, folderResp.getId()); - params = Collections.singletonMap("relativePath", folderA+"/"+folderB+"/"+contentName); + params = Collections.singletonMap(Nodes.PARAM_RELATIVE_PATH, folderA+"/"+folderB+"/"+contentName); response = getSingle(NodesEntityResource.class, user1, Nodes.PATH_MY, params, 200); documentResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); assertEquals(content_Id, documentResp.getId()); @@ -711,7 +711,7 @@ public class NodeApiTest extends AbstractBaseApiTest String folderC = "folder" + System.currentTimeMillis() + " ยข"; String folderC_Id = createFolder(user1, folderB_Id, folderC).getId(); - params = Collections.singletonMap("relativePath", "/"+folderA+"/"+folderB+"/"+folderC); + params = Collections.singletonMap(Nodes.PARAM_RELATIVE_PATH, "/"+folderA+"/"+folderB+"/"+folderC); response = getSingle(NodesEntityResource.class, user1, Nodes.PATH_MY, params, 200); folderResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Folder.class); assertEquals(folderC_Id, folderResp.getId()); @@ -724,15 +724,15 @@ public class NodeApiTest extends AbstractBaseApiTest getSingle(NodesEntityResource.class, user2, myFilesNodeId, null, 403); // -ve test - try to get node info using relative path to unknown node - params = Collections.singletonMap("relativePath", folderA+"/unknown"); + params = Collections.singletonMap(Nodes.PARAM_RELATIVE_PATH, folderA+"/unknown"); getSingle(NodesEntityResource.class, user1, Nodes.PATH_MY, params, 404); // -ve test - try to get node info using relative path to node for which user does not have read permission - params = Collections.singletonMap("relativePath", "User Homes/"+user2); + params = Collections.singletonMap(Nodes.PARAM_RELATIVE_PATH, "User Homes/"+user2); getSingle(NodesEntityResource.class, user1, Nodes.PATH_ROOT, params, 403); // -ve test - attempt to get node info for non-folder node with relative path should return 400 - params = Collections.singletonMap("relativePath", "/unknown"); + params = Collections.singletonMap(Nodes.PARAM_RELATIVE_PATH, "/unknown"); getSingle(NodesEntityResource.class, user1, content_Id, params, 400); } @@ -1216,7 +1216,7 @@ public class NodeApiTest extends AbstractBaseApiTest checkStatus(403, response.getStatusCode()); params = new HashMap<>(); - params.put("relativePath", "/Sites"); + params.put(Nodes.PARAM_RELATIVE_PATH, "/Sites"); response = getSingle(NodesEntityResource.class, user1, rootNodeId, params, 200); nodeResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Node.class); String sitesNodeId = nodeResp.getId(); @@ -1227,7 +1227,7 @@ public class NodeApiTest extends AbstractBaseApiTest checkStatus(403, response.getStatusCode()); params = new HashMap<>(); - params.put("relativePath", "/Data Dictionary"); + params.put(Nodes.PARAM_RELATIVE_PATH, "/Data Dictionary"); response = getSingle(NodesEntityResource.class, user1, rootNodeId, params, 200); nodeResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Node.class); String ddNodeId = nodeResp.getId(); @@ -1364,7 +1364,7 @@ public class NodeApiTest extends AbstractBaseApiTest checkStatus(403, response.getStatusCode()); Map params = new HashMap<>(); - params.put("relativePath", "/Sites"); + params.put(Nodes.PARAM_RELATIVE_PATH, "/Sites"); response = getSingle(NodesEntityResource.class, user1, rootNodeId, params, 200); Node nodeResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Node.class); String sitesNodeId = nodeResp.getId(); @@ -1375,7 +1375,7 @@ public class NodeApiTest extends AbstractBaseApiTest checkStatus(403, response.getStatusCode()); params = new HashMap<>(); - params.put("relativePath", "/Data Dictionary"); + params.put(Nodes.PARAM_RELATIVE_PATH, "/Data Dictionary"); response = getSingle(NodesEntityResource.class, user1, rootNodeId, params, 200); nodeResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Node.class); String ddNodeId = nodeResp.getId(); @@ -1475,8 +1475,6 @@ public class NodeApiTest extends AbstractBaseApiTest @Test public void testCreateFolder() throws Exception { - AuthenticationUtil.setFullyAuthenticatedUser(user1); - String myNodeId = getMyNodeId(user1); UserInfo expectedUser = new UserInfo(user1, user1+" "+user1); @@ -1485,6 +1483,7 @@ public class NodeApiTest extends AbstractBaseApiTest // create folder Folder folderResp = createFolder(user1, myNodeId, "f1"); + String f1Id = folderResp.getId(); Folder f1 = new Folder(); f1.setName("f1"); @@ -1499,12 +1498,13 @@ public class NodeApiTest extends AbstractBaseApiTest f1.expected(folderResp); - // create folder with properties + // create sub-folder with properties Map props = new HashMap<>(); props.put("cm:title","my folder title"); props.put("cm:description","my folder description"); - folderResp = createFolder(user1, myNodeId, "f2", props); + folderResp = createFolder(user1, f1Id, "f2", props); + String f2Id = folderResp.getId(); Folder f2 = new Folder(); f2.setName("f2"); @@ -1512,7 +1512,7 @@ public class NodeApiTest extends AbstractBaseApiTest f2.setProperties(props); f2.setIsFolder(true); - f2.setParentId(myNodeId); + f2.setParentId(f1Id); f2.setAspectNames(Arrays.asList("cm:auditable","cm:titled")); f2.setCreatedByUser(expectedUser); @@ -1520,17 +1520,51 @@ public class NodeApiTest extends AbstractBaseApiTest f2.expected(folderResp); + // create another folder in a (partially existing) folder path + Node n = new Node(); + n.setName("fZ"); + n.setNodeType("cm:folder"); + n.setRelativePath("/f1/f2/f3/f4"); + + // create node + HttpResponse response = post(getNodeChildrenUrl(myNodeId), user1, RestApiUtil.toJsonAsStringNonNull(n), 201); + folderResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Folder.class); + + // check parent hierarchy ... + response = getSingle(NodesEntityResource.class, user1, folderResp.getId(), null, 200); + folderResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Folder.class); + assertEquals(folderResp.getName(),"fZ"); + + response = getSingle(NodesEntityResource.class, user1, folderResp.getParentId(), null, 200); + folderResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Folder.class); + assertEquals(folderResp.getName(),"f4"); + + response = getSingle(NodesEntityResource.class, user1, folderResp.getParentId(), null, 200); + folderResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Folder.class); + assertEquals(folderResp.getName(),"f3"); + + response = getSingle(NodesEntityResource.class, user1, folderResp.getParentId(), null, 200); + folderResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Folder.class); + assertEquals(folderResp.getName(),"f2"); + assertEquals(folderResp.getId(), f2Id); + // -ve test - name is mandatory Folder invalid = new Folder(); invalid.setNodeType("cm:folder"); post(postUrl, user1, toJsonAsStringNonNull(invalid), 400); + // -ve test - invalid name + invalid = new Folder(); + invalid.setName("inv:alid"); + invalid.setNodeType("cm:folder"); + post(postUrl, user1, toJsonAsStringNonNull(invalid), 422); + // -ve test - node type is mandatory invalid = new Folder(); invalid.setName("my folder"); post(postUrl, user1, toJsonAsStringNonNull(invalid), 400); - // create empty file + // create empty file - used in -ve test below Document d1 = new Document(); d1.setName("d1.txt"); d1.setNodeType("cm:content"); @@ -1538,7 +1572,7 @@ public class NodeApiTest extends AbstractBaseApiTest ci.setMimeType("text/plain"); d1.setContent(ci); - HttpResponse response = post(postUrl, user1, toJsonAsStringNonNull(d1), 201); + response = post(postUrl, user1, toJsonAsStringNonNull(d1), 201); Document documentResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); String d1Id = documentResp.getId(); @@ -1572,6 +1606,20 @@ public class NodeApiTest extends AbstractBaseApiTest // -ve test - create a folder with a duplicate name (f1), but set the autoRename to false post(postUrl, user1, toJsonAsStringNonNull(f1), "?autoRename=false", 409); + + // -ve test - invalid relative path + n = new Node(); + n.setName("fX"); + n.setNodeType("cm:folder"); + n.setRelativePath("/f1/inv:alid"); + post(getNodeChildrenUrl(f2Id), user1, RestApiUtil.toJsonAsStringNonNull(n), 422); + + // -ve test - invalid relative path - points to existing node that is not a folder + n = new Node(); + n.setName("fY"); + n.setNodeType("cm:folder"); + n.setRelativePath("d1.txt"); + post(getNodeChildrenUrl(myNodeId), user1, RestApiUtil.toJsonAsStringNonNull(n), 409); } // TODO test custom type with properties (sub-type of cm:cmobject) @@ -1830,6 +1878,33 @@ public class NodeApiTest extends AbstractBaseApiTest d2.expected(documentResp); + // create another empty file in a (partially existing) folder path + Node n = new Node(); + n.setName("d3.txt"); + n.setNodeType("cm:content"); + n.setRelativePath("/f1/f2"); + + // create node + response = post(getNodeChildrenUrl(myNodeId), user1, RestApiUtil.toJsonAsStringNonNull(n), 201); + documentResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + + // check parent hierarchy ... + response = getSingle(NodesEntityResource.class, user1, documentResp.getId(), null, 200); + Folder folderResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Folder.class); + assertEquals(folderResp.getName(),"d3.txt"); + + response = getSingle(NodesEntityResource.class, user1, folderResp.getParentId(), null, 200); + folderResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Folder.class); + assertEquals(folderResp.getName(),"f2"); + + response = getSingle(NodesEntityResource.class, user1, folderResp.getParentId(), null, 200); + folderResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Folder.class); + assertEquals(folderResp.getName(),"f1"); + + response = getSingle(NodesEntityResource.class, user1, folderResp.getParentId(), null, 200); + folderResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Folder.class); + assertEquals(myNodeId, folderResp.getId()); + // -ve test - name is mandatory Document invalid = new Document(); invalid.setNodeType("cm:content"); @@ -2752,13 +2827,13 @@ public class NodeApiTest extends AbstractBaseApiTest String sharedNodeId = getSharedNodeId(user1); Map params = new HashMap<>(); - params.put("relativePath", "/Sites"); + params.put(Nodes.PARAM_RELATIVE_PATH, "/Sites"); HttpResponse response = getSingle(NodesEntityResource.class, user1, rootNodeId, params, 200); Node nodeResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Node.class); String sitesNodeId = nodeResp.getId(); params = new HashMap<>(); - params.put("relativePath", "/Data Dictionary"); + params.put(Nodes.PARAM_RELATIVE_PATH, "/Data Dictionary"); response = getSingle(NodesEntityResource.class, user1, rootNodeId, params, 200); nodeResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Node.class); String ddNodeId = nodeResp.getId(); diff --git a/source/test-java/org/alfresco/rest/api/tests/client/data/Node.java b/source/test-java/org/alfresco/rest/api/tests/client/data/Node.java index 1ebdc69bad..9bd2d1fd05 100644 --- a/source/test-java/org/alfresco/rest/api/tests/client/data/Node.java +++ b/source/test-java/org/alfresco/rest/api/tests/client/data/Node.java @@ -37,6 +37,8 @@ import static org.junit.Assert.assertTrue; /** * Representation of a node - initially for client tests for Nodes (aka File Folder) API * + * TODO push null check down into AssertUtil (making sure that no other existing tests break) + * * @author janv */ public class Node @@ -65,6 +67,8 @@ public class Node protected List allowableOperations; + protected String relativePath; // optionally used in create node request + public Node() { } @@ -219,6 +223,16 @@ public class Node this.allowableOperations = allowableOperations; } + public String getRelativePath() + { + return relativePath; + } + + public void setRelativePath(String relativePath) + { + this.relativePath = relativePath; + } + public void expected(Object o) { Node other = (Node) o; @@ -336,5 +350,14 @@ public class Node { assertNull(other.getAllowableOperations()); } + + if (relativePath != null) + { + assertEquals(relativePath, other.getRelativePath()); + } + else + { + assertNull(other.getRelativePath()); + } } } \ No newline at end of file