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()