diff --git a/config/alfresco/public-rest-context.xml b/config/alfresco/public-rest-context.xml index 36e745d237..12835be9ce 100644 --- a/config/alfresco/public-rest-context.xml +++ b/config/alfresco/public-rest-context.xml @@ -153,6 +153,7 @@ + diff --git a/source/java/org/alfresco/rest/api/Nodes.java b/source/java/org/alfresco/rest/api/Nodes.java index 863c1fa81a..feb411a543 100644 --- a/source/java/org/alfresco/rest/api/Nodes.java +++ b/source/java/org/alfresco/rest/api/Nodes.java @@ -28,21 +28,22 @@ package org.alfresco.rest.api; import java.io.InputStream; import java.util.List; import java.util.Map; -import java.util.Set; - +import java.util.Set; + import org.alfresco.rest.api.model.AssocChild; import org.alfresco.rest.api.model.AssocTarget; -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.Document; +import org.alfresco.rest.api.model.Folder; +import org.alfresco.rest.api.model.LockInfo; +import org.alfresco.rest.api.model.Node; import org.alfresco.rest.api.model.UserInfo; import org.alfresco.rest.framework.resource.content.BasicContentInfo; import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Parameters; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.namespace.QName; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; import org.springframework.extensions.webscripts.servlet.FormData; /** @@ -244,6 +245,15 @@ public interface Nodes * @return */ List addTargets(String sourceNodeId, List entities); + + /** + * Lock a node + * @param nodeId + * @param lockInfo + * @param parameters + * @return + */ + Node lock(String nodeId, LockInfo lockInfo, Parameters parameters); /** * API Constants - query parameters, etc diff --git a/source/java/org/alfresco/rest/api/impl/NodesImpl.java b/source/java/org/alfresco/rest/api/impl/NodesImpl.java index 9e27a56694..0bcc498134 100644 --- a/source/java/org/alfresco/rest/api/impl/NodesImpl.java +++ b/source/java/org/alfresco/rest/api/impl/NodesImpl.java @@ -22,11 +22,33 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . * #L% - */ + */ package org.alfresco.rest.api.impl; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.concurrent.ConcurrentHashMap; + import org.alfresco.model.ApplicationModel; -import org.alfresco.model.ContentModel; +import org.alfresco.model.ContentModel; import org.alfresco.model.QuickShareModel; import org.alfresco.query.PagingRequest; import org.alfresco.query.PagingResults; @@ -34,6 +56,7 @@ import org.alfresco.repo.action.executer.ContentMetadataExtracter; import org.alfresco.repo.activities.ActivityType; import org.alfresco.repo.content.ContentLimitViolationException; import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.lock.mem.Lifetime; import org.alfresco.repo.model.Repository; import org.alfresco.repo.model.filefolder.FileFolderServiceImpl; import org.alfresco.repo.node.getchildren.FilterProp; @@ -53,13 +76,14 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.version.VersionModel; import org.alfresco.rest.antlr.WhereClauseParser; import org.alfresco.rest.api.Activities; -import org.alfresco.rest.api.Nodes; +import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.QuickShareLinks; import org.alfresco.rest.api.model.AssocChild; import org.alfresco.rest.api.model.AssocTarget; -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.Document; +import org.alfresco.rest.api.model.Folder; +import org.alfresco.rest.api.model.LockInfo; +import org.alfresco.rest.api.model.Node; import org.alfresco.rest.api.model.PathInfo; import org.alfresco.rest.api.model.PathInfo.ElementInfo; import org.alfresco.rest.api.model.QuickShareLink; @@ -68,9 +92,9 @@ import org.alfresco.rest.api.nodes.NodeAssocService; import org.alfresco.rest.framework.core.exceptions.ApiException; import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException; import org.alfresco.rest.framework.core.exceptions.DisabledServiceException; -import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; +import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; import org.alfresco.rest.framework.core.exceptions.InsufficientStorageException; -import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; +import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; import org.alfresco.rest.framework.core.exceptions.NotFoundException; import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException; import org.alfresco.rest.framework.core.exceptions.RequestEntityTooLargeException; @@ -95,8 +119,9 @@ import org.alfresco.service.cmr.activities.ActivityInfo; import org.alfresco.service.cmr.activities.ActivityPoster; import org.alfresco.service.cmr.dictionary.AspectDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.model.FileExistsException; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; @@ -109,11 +134,11 @@ import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.MimetypeService; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.Path.Element; -import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.OwnableService; @@ -126,7 +151,7 @@ import org.alfresco.service.cmr.usage.ContentQuotaException; import org.alfresco.service.cmr.version.VersionService; import org.alfresco.service.cmr.version.VersionType; import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; import org.alfresco.util.PropertyCheck; import org.apache.commons.lang.StringUtils; @@ -134,33 +159,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.dao.ConcurrencyFailureException; import org.springframework.extensions.surf.util.Content; -import org.springframework.extensions.webscripts.servlet.FormData; +import org.springframework.extensions.webscripts.servlet.FormData; -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.math.BigInteger; -import java.nio.charset.Charset; -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.StringTokenizer; -import java.util.concurrent.ConcurrentHashMap; - /** - * Centralises access to file/folder/node services and maps between representations. - * + * Centralises access to file/folder/node services and maps between representations. + * * Note: * This class was originally used for returning some basic node info when listing Favourites. * @@ -181,14 +184,12 @@ public class NodesImpl implements Nodes private enum Type { // Note: ordered - DOCUMENT, FOLDER - } - - private static final String DEFAULT_MIMETYPE = MimetypeMap.MIMETYPE_BINARY; - - private NodeService nodeService; + DOCUMENT, FOLDER + } + + private NodeService nodeService; private DictionaryService dictionaryService; - private FileFolderService fileFolderService; + private FileFolderService fileFolderService; private NamespaceService namespaceService; private PermissionService permissionService; private MimetypeService mimetypeService; @@ -200,12 +201,13 @@ public class NodesImpl implements Nodes private AuthorityService authorityService; private ThumbnailService thumbnailService; private SiteService siteService; - private ActivityPoster poster; - private RetryingTransactionHelper retryingTransactionHelper; - private NodeAssocService nodeAssocService; - - private enum Activity_Type - { + private ActivityPoster poster; + private RetryingTransactionHelper retryingTransactionHelper; + private NodeAssocService nodeAssocService; + private LockService lockService; + + private enum Activity_Type + { ADDED, UPDATED, DELETED, DOWNLOADED } @@ -250,12 +252,13 @@ public class NodesImpl implements Nodes this.personService = sr.getPersonService(); this.ownableService = sr.getOwnableService(); this.authorityService = sr.getAuthorityService(); - this.thumbnailService = sr.getThumbnailService(); - this.siteService = sr.getSiteService(); - this.retryingTransactionHelper = sr.getRetryingTransactionHelper(); - - if (defaultIgnoreTypesAndAspects != null) - { + this.thumbnailService = sr.getThumbnailService(); + this.siteService = sr.getSiteService(); + this.retryingTransactionHelper = sr.getRetryingTransactionHelper(); + this.lockService = sr.getLockService(); + + if (defaultIgnoreTypesAndAspects != null) + { ignoreQNames = new HashSet<>(defaultIgnoreTypesAndAspects.size()); for (String type : defaultIgnoreTypesAndAspects) { @@ -908,7 +911,7 @@ public class NodesImpl implements Nodes // special case: do not return "create" (as an allowable op) for file/content types - note: 'type' can be null continue; } - else if (perm.equals(PermissionService.DELETE) && (isSpecialNodeDoNotDelete(nodeRef, nodeTypeQName))) + else if (perm.equals(PermissionService.DELETE) && (isSpecialNode(nodeRef, nodeTypeQName))) { // special case: do not return "delete" (as an allowable op) for specific system nodes continue; @@ -1503,7 +1506,7 @@ public class NodesImpl implements Nodes { NodeRef nodeRef = validateOrLookupNode(nodeId, null); - if (isSpecialNodeDoNotDelete(nodeRef, getNodeType(nodeRef))) + if (isSpecialNode(nodeRef, getNodeType(nodeRef))) { throw new PermissionDeniedException("Cannot delete: " + nodeId); } @@ -1927,9 +1930,9 @@ public class NodesImpl implements Nodes } } - // special case: additional delete validation (pending common lower-level service support) - // for blacklist of system nodes that should not be deleted, eg. Company Home, Sites, Data Dictionary - private boolean isSpecialNodeDoNotDelete(NodeRef nodeRef, QName type) + // special case: additional node validation (pending common lower-level service support) + // for blacklist of system nodes that should not be deleted or locked, eg. Company Home, Sites, Data Dictionary + private boolean isSpecialNode(NodeRef nodeRef, QName type) { // Check for Company Home, Sites and Data Dictionary (note: must be tenant-aware) @@ -2180,7 +2183,7 @@ public class NodesImpl implements Nodes else { // move - if ((! nodeRef.equals(parentNodeRef)) && isSpecialNodeDoNotDelete(nodeRef, getNodeType(nodeRef))) + if ((! nodeRef.equals(parentNodeRef)) && isSpecialNode(nodeRef, getNodeType(nodeRef))) { throw new PermissionDeniedException("Cannot move: "+nodeRef.getId()); } @@ -2900,12 +2903,50 @@ public class NodesImpl implements Nodes { result.add(name); } - } - return result; - } + } + return result; + } + + @Override + public Node lock(String nodeId, LockInfo lockInfo, Parameters parameters) + { + NodeRef nodeRef = validateOrLookupNode(nodeId, null); - /** - * @author Jamal Kaabi-Mofrad + if (isSpecialNode(nodeRef, getNodeType(nodeRef))) + { + throw new PermissionDeniedException("Current user doesn't have permission to lock node " + nodeId); + } + + lockInfo = validateLockInformation(lockInfo); + lockService.lock(nodeRef, lockInfo.getType(), lockInfo.getTimeToExpire(), lockInfo.getLifetime(), lockInfo.getIncludeChildren()); + + return getFolderOrDocument(nodeId, parameters); + } + + private LockInfo validateLockInformation(LockInfo lockInfo) + { + // Set default values for the lock details. + if (lockInfo.getType() == null) + { + lockInfo.setType(LockInfo.LockType2.ALLOW_OWNER_CHANGES.name()); + } + if (lockInfo.getLifetime() == null) + { + lockInfo.setLifetime(Lifetime.PERSISTENT.name()); + } + if (lockInfo.getIncludeChildren() == null) + { + lockInfo.setIncludeChildren(false); + } + if (lockInfo.getTimeToExpire() == null) + { + lockInfo.setTimeToExpire(0); + } + return lockInfo; + } + + /** + * @author Jamal Kaabi-Mofrad */ /* private static class ContentInfoWrapper implements BasicContentInfo diff --git a/source/java/org/alfresco/rest/api/model/LockInfo.java b/source/java/org/alfresco/rest/api/model/LockInfo.java new file mode 100644 index 0000000000..db14ebf911 --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/LockInfo.java @@ -0,0 +1,120 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.rest.api.model; + +import org.alfresco.repo.lock.mem.Lifetime; +import org.alfresco.service.cmr.lock.LockType; + +/** + * Representation of a lock info + * + * @author Ancuta Morarasu + */ +public class LockInfo +{ + private Integer timeToExpire; + private Boolean includeChildren; + private LockType2 type; + private Lifetime lifetime; + + /** + * Lock Type enum that maps to the current values in {@link org.alfresco.service.cmr.lock.LockType}. + * These values describe better the meanings of the lock types. + */ + @SuppressWarnings("deprecation") + public static enum LockType2 + { + FULL(LockType.READ_ONLY_LOCK), + ALLOW_ADD_CHILDREN(LockType.NODE_LOCK), + ALLOW_OWNER_CHANGES(LockType.WRITE_LOCK); + + private LockType type; + + private LockType2(LockType type) + { + this.type = type; + } + public LockType getType() + { + return type; + } + } + + public LockInfo() {} + + public void setTimeToExpire(Integer timeToExpire) + { + this.timeToExpire = timeToExpire; + } + + public Integer getTimeToExpire() + { + return timeToExpire; + } + + public void setIncludeChildren(Boolean includeChildren) + { + this.includeChildren = includeChildren; + } + + public Boolean getIncludeChildren() + { + return includeChildren; + } + + public LockType getType() + { + return type != null ? type.getType() : null; + } + + public void setType(String type) + { + this.type = LockType2.valueOf(type); + } + + public Lifetime getLifetime() + { + return lifetime; + } + + public void setLifetime(String lifetimeStr) + { + this.lifetime = Lifetime.valueOf(lifetimeStr); + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder("LockInfo{"); + sb.append("includeChildren='").append(includeChildren).append('\''); + sb.append(", timeToExpire=").append(timeToExpire).append('\''); + sb.append(", type=").append(type).append('\''); + sb.append(", lifetime=").append(lifetime).append('\''); + sb.append('}'); + return sb.toString(); + } + +} diff --git a/source/java/org/alfresco/rest/api/nodes/NodesEntityResource.java b/source/java/org/alfresco/rest/api/nodes/NodesEntityResource.java index f9e9f751a3..64a932cf73 100644 --- a/source/java/org/alfresco/rest/api/nodes/NodesEntityResource.java +++ b/source/java/org/alfresco/rest/api/nodes/NodesEntityResource.java @@ -25,28 +25,29 @@ */ package org.alfresco.rest.api.nodes; -import org.alfresco.rest.api.Nodes; -import org.alfresco.rest.api.model.Node; -import org.alfresco.rest.api.model.NodeTarget; -import org.alfresco.rest.framework.Operation; -import org.alfresco.rest.framework.BinaryProperties; -import org.alfresco.rest.framework.WebApiDescription; -import org.alfresco.rest.framework.WebApiParam; -import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; -import org.alfresco.rest.framework.resource.EntityResource; -import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction; -import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction; -import org.alfresco.rest.framework.resource.content.BasicContentInfo; -import org.alfresco.rest.framework.resource.content.BinaryResource; -import org.alfresco.rest.framework.resource.parameters.Parameters; -import org.alfresco.rest.framework.webscripts.WithResponse; -import org.alfresco.util.ParameterCheck; -import org.apache.lucene.store.Lock; +import java.io.InputStream; + +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.rest.api.Nodes; +import org.alfresco.rest.api.model.LockInfo; +import org.alfresco.rest.api.model.Node; +import org.alfresco.rest.api.model.NodeTarget; +import org.alfresco.rest.framework.BinaryProperties; +import org.alfresco.rest.framework.Operation; +import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.WebApiParam; +import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; +import org.alfresco.rest.framework.resource.EntityResource; +import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction; +import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction; +import org.alfresco.rest.framework.resource.content.BasicContentInfo; +import org.alfresco.rest.framework.resource.content.BinaryResource; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.rest.framework.webscripts.WithResponse; +import org.alfresco.util.ParameterCheck; import org.springframework.beans.factory.InitializingBean; -import javax.servlet.http.HttpServletResponse; -import java.io.InputStream; - /** * An implementation of an Entity Resource for a Node (file or folder) * @@ -169,6 +170,15 @@ public class NodesEntityResource implements { return nodes.moveOrCopyNode(nodeId, target.getTargetParentId(), target.getName(), parameters, false); } + + @Operation("lock") + @WebApiDescription(title = "Lock Node", + description="Places a lock on a node.", + successStatus = HttpServletResponse.SC_OK) + public Node lock(String nodeId, LockInfo lockInfo, Parameters parameters, WithResponse withResponse) + { + return nodes.lock(nodeId, lockInfo, parameters); + } } 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 79ea7be7c4..2112ef5c25 100644 --- a/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java +++ b/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java @@ -35,6 +35,21 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; + import org.alfresco.repo.content.ContentLimitProvider.SimpleFixedLimitProvider; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; @@ -57,7 +72,6 @@ import org.alfresco.rest.api.tests.client.data.Folder; import org.alfresco.rest.api.tests.client.data.Node; import org.alfresco.rest.api.tests.client.data.PathInfo; import org.alfresco.rest.api.tests.client.data.PathInfo.ElementInfo; -import org.alfresco.rest.api.tests.client.data.SiteMember; import org.alfresco.rest.api.tests.client.data.SiteRole; import org.alfresco.rest.api.tests.client.data.UserInfo; import org.alfresco.rest.api.tests.util.MultiPartBuilder; @@ -70,26 +84,12 @@ import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.site.SiteVisibility; import org.alfresco.util.GUID; import org.alfresco.util.TempFileProvider; +import org.apache.http.HttpStatus; import org.json.simple.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Test; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.UUID; - /** * V1 REST API tests for Nodes (files, folders and custom node types) * @@ -3573,6 +3573,138 @@ public class NodeApiTest extends AbstractSingleNetworkSiteTest // some cleanup deleteNode(folderId, true, 204); } + + /** + * Tests lock of a node + *

POST:

+ * {@literal :/alfresco/api/-default-/public/alfresco/versions/1/nodes//lock} + */ + @Test + public void testLock() throws Exception + { + setRequestContext(user1); + + // create folder + Folder folderResp = createFolder(Nodes.PATH_MY, "folderT"); + String folderId = folderResp.getId(); + + // create doc d1 + String d1Name = "content" + RUNID + "_1l"; + Document d1 = createTextFile(folderId, d1Name, "The quick brown fox jumps over the lazy dog 1."); + String d1Id = d1.getId(); + + Map body = new HashMap<>(); + body.put("includeChildren", "true"); + body.put("timeToExpire", "60"); + body.put("type", "FULL"); + body.put("lifetime", "PERSISTENT"); + + HttpResponse response = post(URL_NODES, d1Id, "lock", toJsonAsStringNonNull(body).getBytes(), null, null, 200); + Document documentResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + + assertEquals(d1Name, documentResp.getName()); + assertEquals(d1Id, documentResp.getId()); + assertEquals("READ_ONLY_LOCK", documentResp.getProperties().get("cm:lockType")); + assertNotNull(documentResp.getProperties().get("cm:lockOwner")); + + // Empty lock body, the default values are used + post("nodes/"+folderId+"/lock", "{}", null, 200); + + // Test delete on a folder which contains a locked node - NodeLockedException + deleteNode(folderId, true, HttpStatus.SC_CONFLICT); + + // Test lock children + // create folder + Folder folderA = createFolder(Nodes.PATH_MY, "folderA"); + String folderAId = folderA.getId(); + + // create 2 children files + String dA1Name = "content" + RUNID + "_A1"; + Document dA1 = createTextFile(folderAId, dA1Name, "A1 content"); + String dA1Id = dA1.getId(); + + String dA2Name = "content" + RUNID + "_A2"; + Document dA2 = createTextFile(folderId, dA2Name, "A2 content"); + String dA2Id = dA2.getId(); + + body = new HashMap<>(); + body.put("includeChildren", "true"); + body.put("timeToExpire", "60"); + body.put("type", "FULL"); + body.put("lifetime", "EPHEMERAL"); + + // lock the folder + response = post(URL_NODES, folderAId, "lock", toJsonAsStringNonNull(body).getBytes(), null, null, 200); + documentResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + + assertEquals("folderA", documentResp.getName()); + assertEquals(folderAId, documentResp.getId()); + assertNotNull(documentResp.getProperties().get("cm:lockType")); + assertNotNull(documentResp.getProperties().get("cm:lockOwner")); + + Map params = Collections.singletonMap("include", "aspectNames,properties"); + response = getAll(getNodeChildrenUrl(folderAId), null, params, 200); + List nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Node.class); + // Test if children nodes are locked as well. + for (Node child : nodes) + { + assertNotNull(child.getProperties().get("cm:lockType")); + assertNotNull(child.getProperties().get("cm:lockOwner")); + } + + Folder folderB = createFolder(Nodes.PATH_MY, "folderB"); + String folderBId = folderB.getId(); + + body = new HashMap<>(); + body.put("timeToExpire", "-100"); // values lower than 0 are considered as no expiry time + post("nodes/" + folderBId + "/lock", toJsonAsStringNonNull(body), null, 200); + + // -ve tests + + // Missing target node + body = new HashMap<>(); + body.put("timeToExpire", "60"); + + post("nodes/" + "fakeId" + "/lock", toJsonAsStringNonNull(body), null, 404); + + // Cannot lock Data Dictionary node + params = new HashMap<>(); + params.put(Nodes.PARAM_RELATIVE_PATH, "/Data Dictionary"); + response = getSingle(NodesEntityResource.class, getRootNodeId(), params, 200); + Node nodeResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Node.class); + String ddNodeId = nodeResp.getId(); + + setRequestContext(networkAdmin); + post("nodes/"+ddNodeId+"/lock", toJsonAsStringNonNull(body), null, 403); + + // Lock node already locked by another user - UnableToAquireLockException + post("nodes/"+folderId+"/lock", "{}", null, 422); + + // Invalid lock body values + setRequestContext(user1); + + Folder folderC = createFolder(Nodes.PATH_MY, "folderC"); + String folderCId = folderB.getId(); + body = new HashMap<>(); + body.put("includeChildren", "true123"); + post("nodes/"+folderBId+"/lock", toJsonAsStringNonNull(body), null, 400); + + body = new HashMap<>(); + body.put("type", "FULL123"); + post("nodes/"+folderBId+"/lock", toJsonAsStringNonNull(body), null, 400); + + body = new HashMap<>(); + body.put("lifetime", "PERSISTENT123"); + post("nodes/"+folderBId+"/lock", toJsonAsStringNonNull(body), null, 400); + + body = new HashMap<>(); + body.put("timeToExpire", "NaN"); + post("nodes/"+folderBId+"/lock", toJsonAsStringNonNull(body), null, 400); + + body = new HashMap<>(); + body.put("invalid_property", "true"); + post("nodes/"+folderBId+"/lock", toJsonAsStringNonNull(body), null, 400); + } @Override public String getScope()