From 9ebe1f7c00e5621dfd4a085b98330ac561c86a1e Mon Sep 17 00:00:00 2001 From: Ancuta Morarasu Date: Wed, 11 May 2016 10:51:44 +0000 Subject: [PATCH] Merged HEAD (5.2) to 5.2.N (5.2.1) 126376 jkaabimofrad: Merged FILE-FOLDER-API (5.2.0) to HEAD (5.2) 119921 jkaabimofrad: Finished the requirements in RA-637. Added nodeType form data, the client provided content-type will be used if it is not null, and also support for model properties in the form data. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.2.N/root@126722 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../org/alfresco/rest/api/impl/NodesImpl.java | 121 ++++++++++++------ .../alfresco/rest/api/tests/NodeApiTest.java | 65 +++++++++- .../rest/api/tests/util/MultiPartBuilder.java | 41 +++++- 3 files changed, 179 insertions(+), 48 deletions(-) diff --git a/source/java/org/alfresco/rest/api/impl/NodesImpl.java b/source/java/org/alfresco/rest/api/impl/NodesImpl.java index a542c68fbc..1b6c2a11ff 100644 --- a/source/java/org/alfresco/rest/api/impl/NodesImpl.java +++ b/source/java/org/alfresco/rest/api/impl/NodesImpl.java @@ -566,6 +566,11 @@ public class NodesImpl implements Nodes QName typeQName = nodeService.getType(nodeRef); + return getFolderOrDocumentFullInfo(nodeRef, getParentNodeRef(nodeRef), typeQName, parameters); + } + + private Node getFolderOrDocumentFullInfo(NodeRef nodeRef, NodeRef parentNodeRef, QName nodeTypeQName, Parameters parameters) + { List selectParam = new ArrayList<>(); selectParam.addAll(parameters.getSelectedProperties()); @@ -573,7 +578,7 @@ public class NodesImpl implements Nodes selectParam.add(PARAM_SELECT_ASPECTNAMES); selectParam.add(PARAM_SELECT_PROPERTIES); - return getFolderOrDocument(nodeRef, getParentNodeRef(nodeRef), typeQName, selectParam, null); + return getFolderOrDocument(nodeRef, parentNodeRef, nodeTypeQName, selectParam, null); } private Node getFolderOrDocument(final NodeRef nodeRef, NodeRef parentNodeRef, QName nodeTypeQName, List selectParam, Map mapUserInfo) @@ -743,7 +748,7 @@ public class NodesImpl implements Nodes for (Entry entry : props.entrySet()) { - QName propQName = QName.createQName(entry.getKey(), namespaceService); + QName propQName = createQName(entry.getKey()); PropertyDefinition pd = dictionaryService.getProperty(propQName); if (pd != null) @@ -977,19 +982,8 @@ public class NodesImpl implements Nodes props = mapToNodeProperties(nodeInfo.getProperties()); } - props.put(ContentModel.PROP_NAME, nodeName); - - QName assocQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(nodeName)); - - NodeRef nodeRef; - try - { - nodeRef = nodeService.createNode(parentNodeRef, ContentModel.ASSOC_CONTAINS, assocQName, nodeTypeQName, props).getChildRef(); - } - catch (DuplicateChildNodeNameException dcne) - { - throw new ConstraintViolatedException(dcne.getMessage()); - } + // Create the node + NodeRef nodeRef = createNodeImpl(parentNodeRef, nodeName, nodeTypeQName, props); List aspectNames = nodeInfo.getAspectNames(); if (aspectNames != null) @@ -997,7 +991,7 @@ public class NodesImpl implements Nodes // node aspects - set any additional aspects for (String aspectName : aspectNames) { - QName aspectQName = QName.createQName(aspectName, namespaceService); + QName aspectQName = createQName(aspectName); if (EXCLUDED_ASPECTS.contains(aspectQName) || aspectQName.equals(ContentModel.ASPECT_AUDITABLE)) { continue; // ignore @@ -1018,6 +1012,25 @@ public class NodesImpl implements Nodes return getFolderOrDocument(nodeRef.getId(), parameters); } + private NodeRef createNodeImpl(NodeRef parentNodeRef, String nodeName, QName nodeTypeQName, Map props) + { + if(props == null) + { + props = new HashMap<>(1); + } + props.put(ContentModel.PROP_NAME, nodeName); + + QName assocQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(nodeName)); + try + { + return nodeService.createNode(parentNodeRef, ContentModel.ASSOC_CONTAINS, assocQName, nodeTypeQName, props).getChildRef(); + } + catch (DuplicateChildNodeNameException dcne) + { + throw new ConstraintViolatedException(dcne.getMessage()); + } + } + public Node updateNode(String nodeId, Node nodeInfo, Parameters parameters) { final NodeRef nodeRef = validateNode(nodeId); @@ -1048,7 +1061,7 @@ public class NodesImpl implements Nodes if ((nodeType != null) && (! nodeType.isEmpty())) { // update node type - ensure that we are performing a specialise (we do not support generalise) - QName destNodeTypeQName = QName.createQName(nodeType, namespaceService); + QName destNodeTypeQName = createQName(nodeType); if ((! destNodeTypeQName.equals(nodeTypeQName)) && dictionaryService.isSubClass(destNodeTypeQName, nodeTypeQName)) { @@ -1067,7 +1080,7 @@ public class NodesImpl implements Nodes Set aspectQNames = new HashSet<>(aspectNames.size()); for (String aspectName : aspectNames) { - QName aspectQName = QName.createQName(aspectName, namespaceService); + QName aspectQName = createQName(aspectName); aspectQNames.add(aspectQName); } @@ -1186,7 +1199,10 @@ public class NodesImpl implements Nodes String fileName = null; Content content = null; boolean autoRename = false; + QName nodeTypeQName = null; boolean overwrite = false; // If a fileName clashes for a versionable file + Map qnameStrProps = new HashMap<>(); + Map properties = null; for (FormData.FormField field : formData.getFields()) { @@ -1208,27 +1224,50 @@ public class NodesImpl implements Nodes autoRename = Boolean.valueOf(field.getValue()); break; + case "nodetype": + nodeTypeQName = createQName(getStringOrNull(field.getValue())); + if (!dictionaryService.isSubClass(nodeTypeQName, ContentModel.TYPE_CONTENT)) + { + throw new InvalidArgumentException("Can only upload type of cm:content: " + nodeTypeQName); + } + break; + // case "overwrite": // overwrite = Boolean.valueOf(field.getValue()); // break; + + default: + { + final String propName = field.getName(); + if (propName.indexOf(QName.NAMESPACE_PREFIX) > -1) + { + qnameStrProps.put(propName, field.getValue()); + } + } } } + // MNT-7213 When alf_data runs out of disk space, Share uploads + // result in a success message, but the files do not appear. + if (formData.getFields().length == 0) + { + throw new ConstraintViolatedException(" No disk space available"); + } + // Ensure mandatory file attributes have been located. Need either + // destination, or site + container or updateNodeRef + if ((fileName == null || content == null)) + { + throw new InvalidArgumentException("Required parameters are missing"); + } + try { - // MNT-7213 When alf_data runs out of disk space, Share uploads - // result in a success message, but the files do not appear. - if (formData.getFields().length == 0) + // Map the given properties, if any. + if (qnameStrProps.size() > 0) { - throw new ConstraintViolatedException(" No disk space available"); + properties = mapToNodeProperties(qnameStrProps); } - // Ensure mandatory file attributes have been located. Need either - // destination, or site + container or updateNodeRef - if ((fileName == null || content == null)) - { - throw new InvalidArgumentException("Required parameters are missing"); - } /* * Existing file handling */ @@ -1262,7 +1301,7 @@ public class NodesImpl implements Nodes } // Create a new file. - return createNewFile(parentNodeRef, fileName, content); + return createNewFile(parentNodeRef, fileName, nodeTypeQName, content, properties, parameters); // Do not clean formData temp files to allow for retries. // Temp files will be deleted later when GC call DiskFileItem#finalize() method or by temp file cleaner. @@ -1299,13 +1338,16 @@ 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, Content content) + private Node createNewFile(NodeRef parentNodeRef, String fileName, QName nodeType, Content content, Map props, Parameters params) { - FileInfo fileInfo = fileFolderService.create(parentNodeRef, fileName, ContentModel.TYPE_CONTENT); - NodeRef newFile = fileInfo.getNodeRef(); + if (nodeType == null) + { + nodeType = ContentModel.TYPE_CONTENT; + } + NodeRef newFile = createNodeImpl(parentNodeRef, fileName, nodeType, props); // Write content - write(newFile, content, fileName, false, true); + write(newFile, content, fileName, true, true); // Ensure the file is versionable (autoVersion = true, autoVersionProps = false) ensureVersioningEnabled(newFile, true, false); @@ -1314,12 +1356,7 @@ public class NodesImpl implements Nodes extractMetadata(newFile); // Create the response - return createUploadResponse(parentNodeRef, newFile); - } - - private Node createUploadResponse(NodeRef parentNodeRef, NodeRef newFileNodeRef) - { - return getFolderOrDocument(newFileNodeRef, parentNodeRef, ContentModel.TYPE_CONTENT, Collections.emptyList(), null); + return getFolderOrDocumentFullInfo(newFile, parentNodeRef, nodeType, params); } private String getStringOrNull(String value) @@ -1338,7 +1375,7 @@ public class NodesImpl implements Nodes * @param content the content * @param fileName the uploaded file name * @param applyMimeType If true, apply the mimeType from the Content object, - * else leave the original mimeType + * else leave the original mimeType (if the original type is null, then guess the mimeType) * @param guessEncoding If true, guess the encoding from the underlying * input stream, else use encoding set in the Content object as supplied */ @@ -1347,7 +1384,9 @@ public class NodesImpl implements Nodes ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); InputStream is; String mimeType = content.getMimetype(); - if (!applyMimeType) + // Per RA-637 requirement the mimeType provided by the client takes precedent, however, + // if the mimeType is null, then it will be retrieved or guessed. + if (mimeType == null || !applyMimeType) { ContentData existingContentData = (ContentData) nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); if (existingContentData != null) 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 e808fe6d4d..8efe0ee00a 100644 --- a/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java +++ b/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java @@ -554,6 +554,9 @@ public class NodeApiTest extends AbstractBaseApiTest ContentInfo contentInfo = document.getContent(); assertNotNull(contentInfo); assertEquals(MimetypeMap.MIMETYPE_PDF, contentInfo.getMimeType()); + // Check there is no path info returned. + // The path info should only be returned when it is requested via a select statement. + assertNull(document.getPath()); // Retrieve the uploaded file response = getSingle(NodesEntityResource.class, user1, document.getId(), null, 200); @@ -587,11 +590,12 @@ public class NodeApiTest extends AbstractBaseApiTest // Check the upload response assertEquals("quick-1.pdf", document.getName()); - // upload the same file again - response = post(getChildrenUrl(Nodes.PATH_MY), user1, new String(reqBody.getBody()), null, reqBody.getContentType(), 201); + // upload the same file again, and request the path info to be present in the response + response = post(getChildrenUrl(Nodes.PATH_MY), user1, new String(reqBody.getBody()), "?select=path", reqBody.getContentType(), 201); document = jacksonUtil.parseEntry(response.getJsonResponse(), Document.class); // Check the upload response assertEquals("quick-2.pdf", document.getName()); + assertNotNull(document.getPath()); response = getAll(getChildrenUrl(Nodes.PATH_MY), user1, paging, 200); pagingResult = parsePaging(response.getJsonResponse()); @@ -619,6 +623,15 @@ public class NodeApiTest extends AbstractBaseApiTest // Try to upload a file without defining the required formData reqBody = MultiPartBuilder.create().setAutoRename(true).build(); post(getChildrenUrl(Nodes.PATH_MY), user1, new String(reqBody.getBody()), null, reqBody.getContentType(), 400); + + // Test unsupported node type + reqBody = MultiPartBuilder.create() + .setFileData(new FileData(fileName2, file2, null)) + .setAutoRename(true) + .setNodeType("cm:link") + .build(); + post(getChildrenUrl(user1Home.getId()), user1, new String(reqBody.getBody()), null, reqBody.getContentType(), 400); + } /** @@ -643,7 +656,7 @@ public class NodeApiTest extends AbstractBaseApiTest final int numOfNodes = pagingResult.getCount().intValue(); MultiPartBuilder multiPartBuilder = MultiPartBuilder.create() - .setFileData(new FileData(fileName, file, MimetypeMap.MIMETYPE_TEXT_PLAIN)); + .setFileData(new FileData(fileName, file, null)); MultiPartRequest reqBody = multiPartBuilder.build(); // Try to upload response = post(getChildrenUrl(folderA_Ref), userOneN1.getId(), new String(reqBody.getBody()), null, reqBody.getContentType(), 201); @@ -652,6 +665,7 @@ public class NodeApiTest extends AbstractBaseApiTest assertEquals(fileName, document.getName()); ContentInfo contentInfo = document.getContent(); assertNotNull(contentInfo); + // As the client didn't set the mimeType, the API must guess it. assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, contentInfo.getMimeType()); // Retrieve the uploaded file @@ -683,6 +697,51 @@ public class NodeApiTest extends AbstractBaseApiTest .build(); // userTwoN1 tries to upload a new file into the folderA of userOneN1 post(getChildrenUrl(folderA_Ref), userTwoN1.getId(), new String(reqBody.getBody()), null, reqBody.getContentType(), 403); + + // Test upload with properties + response = post(getChildrenUrl(folderA_Ref), userOneN1.getId(), new String(reqBody.getBody()), null, reqBody.getContentType(), 201); + document = jacksonUtil.parseEntry(response.getJsonResponse(), Document.class); + // Check the upload response + assertEquals(fileName2, document.getName()); + contentInfo = document.getContent(); + assertNotNull(contentInfo); + assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, contentInfo.getMimeType()); + assertNotNull(document.getProperties()); + assertNull(document.getProperties().get("cm:title")); + assertNull(document.getProperties().get("cm:description")); + + // upload a file with properties. Also, set autoRename=true + Map props = new HashMap<>(2); + props.put("cm:title", "test title"); + props.put("cm:description", "test description"); + reqBody = MultiPartBuilder.create() + .setFileData(new FileData(fileName2, file2, MimetypeMap.MIMETYPE_TEXT_PLAIN)) + .setAutoRename(true) + .setProperties(props) + .build(); + + response = post(getChildrenUrl(folderA_Ref), userOneN1.getId(), new String(reqBody.getBody()), null, reqBody.getContentType(), 201); + document = jacksonUtil.parseEntry(response.getJsonResponse(), Document.class); + // Check the upload response + // "quick-2-1.txt" => fileName2 + autoRename + assertEquals("quick-2-1.txt", document.getName()); + contentInfo = document.getContent(); + assertNotNull(contentInfo); + assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, contentInfo.getMimeType()); + assertNotNull(document.getProperties()); + assertEquals("test title", document.getProperties().get("cm:title")); + assertEquals("test description", document.getProperties().get("cm:description")); + + // Test unknown property name + props = new HashMap<>(1); + props.put("unknownPrefix" + System.currentTimeMillis() + ":description", "test description"); + reqBody = MultiPartBuilder.create() + .setFileData(new FileData(fileName2, file2, MimetypeMap.MIMETYPE_TEXT_PLAIN)) + .setAutoRename(true) + .setProperties(props) + .build(); + // Prop prefix is unknown + post(getChildrenUrl(folderA_Ref), userOneN1.getId(), new String(reqBody.getBody()), null, reqBody.getContentType(), 400); } /** diff --git a/source/test-java/org/alfresco/rest/api/tests/util/MultiPartBuilder.java b/source/test-java/org/alfresco/rest/api/tests/util/MultiPartBuilder.java index 0c245905e5..f205c3c219 100644 --- a/source/test-java/org/alfresco/rest/api/tests/util/MultiPartBuilder.java +++ b/source/test-java/org/alfresco/rest/api/tests/util/MultiPartBuilder.java @@ -37,7 +37,11 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; /** * multipart/form-data builder. @@ -54,10 +58,12 @@ public class MultiPartBuilder private String updateNodeRef; private String description; private String contentTypeQNameStr; - private List aspects; + private List aspects = Collections.emptyList(); private Boolean majorVersion; private Boolean overwrite; private Boolean autoRename; + private String nodeType; + private Map properties = Collections.emptyMap(); private MultiPartBuilder() { @@ -73,10 +79,12 @@ public class MultiPartBuilder this.updateNodeRef = that.updateNodeRef; this.description = that.description; this.contentTypeQNameStr = that.contentTypeQNameStr; - this.aspects = that.aspects; + this.aspects = new ArrayList<>(that.aspects); this.majorVersion = that.majorVersion; this.overwrite = that.overwrite; this.autoRename = that.autoRename; + this.nodeType = that.nodeType; + this.properties = new HashMap<>(that.properties); } public static MultiPartBuilder create() @@ -161,9 +169,21 @@ public class MultiPartBuilder return this; } + public MultiPartBuilder setNodeType(String nodeType) + { + this.nodeType = nodeType; + return this; + } + + public MultiPartBuilder setProperties(Map properties) + { + this.properties = properties; + return this; + } + private String getAspects(List aspects) { - if (aspects != null) + if (!aspects.isEmpty()) { StringBuilder sb = new StringBuilder(aspects.size() * 2); for (String str : aspects) @@ -243,7 +263,11 @@ public class MultiPartBuilder if (fileData != null) { - parts.add(new FilePart("filedata", fileData.getFileName(), fileData.getFile(), fileData.getMimetype(), null)); + FilePart fp = new FilePart("filedata", fileData.getFileName(), fileData.getFile(), fileData.getMimetype(), null); + // Get rid of the default values added upon FilePart instantiation + fp.setCharSet(null); + fp.setContentType(fileData.getMimetype()); + parts.add(fp); addPartIfNotNull(parts, "filename", fileData.getFileName()); } addPartIfNotNull(parts, "siteid", siteId); @@ -257,6 +281,15 @@ public class MultiPartBuilder addPartIfNotNull(parts, "majorversion", majorVersion); addPartIfNotNull(parts, "overwrite", overwrite); addPartIfNotNull(parts, "autoRename", autoRename); + addPartIfNotNull(parts, "nodeType", nodeType); + + if (!properties.isEmpty()) + { + for (Entry prop : properties.entrySet()) + { + parts.add(new StringPart(prop.getKey(), prop.getValue())); + } + } MultipartRequestEntity req = new MultipartRequestEntity(parts.toArray(new Part[parts.size()]), new HttpMethodParams());